1 /* Copyright (c) 2007 Mark Nevill
2  *
3  * Permission is hereby granted, free of charge, to any person
4  * obtaining a copy of this software and associated documentation
5  * files (the "Software"), to deal in the Software without
6  * restriction, including without limitation the rights to use,
7  * copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following
10  * conditions:
11  *
12  * The above copyright notice and this permission notice shall be
13  * included in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22  * OTHER DEALINGS IN THE SOFTWARE.
23  */
24 
25 /** @file basedir.c
26   * @brief Implementation of the XDG Base Directory specification. */
27 
28 #if defined(HAVE_CONFIG_H) || defined(_DOXYGEN)
29 #include <config.h>
30 #endif
31 
32 #if STDC_HEADERS || HAVE_STDLIB_H || !defined(HAVE_CONFIG_H)
33 #  include <stdlib.h>
34 #endif
35 #if HAVE_MEMORY_H || !defined(HAVE_CONFIG_H)
36 #  include <memory.h>
37 #endif
38 #if HAVE_STRING_H || !defined(HAVE_CONFIG_H)
39 #  include <string.h>
40 #endif
41 #if HAVE_STRINGS_H
42 #  include <strings.h>
43 #endif
44 
45 #include <sys/stat.h>
46 #include <sys/types.h>
47 
48 #include <errno.h>
49 
50 #ifdef FALSE
51 #undef FALSE
52 #endif
53 #ifdef TRUE
54 #undef TRUE
55 #endif
56 #define FALSE 0
57 #define TRUE 1
58 
59 #if HAVE_MEMSET || !defined(HAVE_CONFIG_H)
60 #  define xdgZeroMemory(p, n) memset(p, 0, n)
61 #elif HAVE_BZERO
62 #  define xdgZeroMemory(p, n) bzero(p, n)
63 #else
xdgZeroMemory(void * p,size_t n)64 static void xdgZeroMemory(void* p, size_t n)
65 {
66 	while (n > 0) { ((char*)p)[n] = 0; ++n; }
67 }
68 #endif
69 
70 #if defined _WIN32 && !defined __CYGWIN__
71    /* Use Windows separators on all _WIN32 defining
72       environments, except Cygwin. */
73 #  define DIR_SEPARATOR_CHAR		'\\'
74 #  define DIR_SEPARATOR_STR		"\\"
75 #  define PATH_SEPARATOR_CHAR		';'
76 #  define PATH_SEPARATOR_STR		";"
77 #  define NO_ESCAPES_IN_PATHS
78 #else
79 #  define DIR_SEPARATOR_CHAR		'/'
80 #  define DIR_SEPARATOR_STR		"/"
81 #  define PATH_SEPARATOR_CHAR		':'
82 #  define PATH_SEPARATOR_STR		":"
83 #  define NO_ESCAPES_IN_PATHS
84 #endif
85 
86 #include <basedir.h>
87 #include <basedir_fs.h>
88 
89 #ifndef MAX
90 #define MAX(a, b) ((b) > (a) ? (b) : (a))
91 #endif
92 
93 static const char
94 	DefaultRelativeDataHome[] = DIR_SEPARATOR_STR ".local" DIR_SEPARATOR_STR "share",
95 	DefaultRelativeConfigHome[] = DIR_SEPARATOR_STR ".config",
96 	DefaultDataDirectories1[] = DIR_SEPARATOR_STR "usr" DIR_SEPARATOR_STR "local" DIR_SEPARATOR_STR "share",
97 	DefaultDataDirectories2[] = DIR_SEPARATOR_STR "usr" DIR_SEPARATOR_STR "share",
98 	DefaultConfigDirectories[] = DIR_SEPARATOR_STR "etc" DIR_SEPARATOR_STR "xdg",
99 	DefaultRelativeCacheHome[] = DIR_SEPARATOR_STR ".cache";
100 
101 static const char
102 	*DefaultDataDirectoriesList[] = { DefaultDataDirectories1, DefaultDataDirectories2, NULL },
103 	*DefaultConfigDirectoriesList[] = { DefaultConfigDirectories, NULL };
104 
105 typedef struct _xdgCachedData
106 {
107 	char * dataHome;
108 	char * configHome;
109 	char * cacheHome;
110 	/* Note: string lists are null-terminated and all items */
111 	/* except the first are assumed to be allocated using malloc. */
112 	/* The first item is assumed to be allocated by malloc only if */
113 	/* it is not equal to the appropriate home directory string above. */
114 	char ** searchableDataDirectories;
115 	char ** searchableConfigDirectories;
116 } xdgCachedData;
117 
118 /** Get cache object associated with a handle */
xdgGetCache(xdgHandle * handle)119 static xdgCachedData* xdgGetCache(xdgHandle *handle)
120 {
121 	return ((xdgCachedData*)(handle->reserved));
122 }
123 
xdgInitHandle(xdgHandle * handle)124 xdgHandle * xdgInitHandle(xdgHandle *handle)
125 {
126 	if (!handle) return 0;
127 	handle->reserved = 0; /* So xdgUpdateData() doesn't free it */
128 	if (xdgUpdateData(handle))
129 		return handle;
130 	return 0;
131 }
132 
133 /** Free all memory used by a NULL-terminated string list */
xdgFreeStringList(char ** list)134 static void xdgFreeStringList(char** list)
135 {
136 	char** ptr = list;
137 	if (!list) return;
138 	for (; *ptr; ptr++)
139 		free(*ptr);
140 	free(list);
141 }
142 
143 /** Free all data in the cache and set pointers to null. */
xdgFreeData(xdgCachedData * cache)144 static void xdgFreeData(xdgCachedData *cache)
145 {
146      /* if (cache->dataHome) */
147 	{
148 		/* the first element of the directory lists is usually the home directory */
149 		if (cache->searchableDataDirectories[0] != cache->dataHome)
150 			free(cache->dataHome);
151 		cache->dataHome = NULL;
152 	}
153      /* if (cache->configHome) */
154 	{
155 		if (cache->searchableConfigDirectories[0] != cache->configHome)
156 			free(cache->configHome);
157 		cache->configHome = NULL;
158 	}
159 	if (cache->cacheHome)
160 	{
161 		free(cache->cacheHome);
162 		cache->cacheHome = NULL;
163 	}
164 	xdgFreeStringList(cache->searchableDataDirectories);
165 	cache->searchableDataDirectories = NULL;
166 	xdgFreeStringList(cache->searchableConfigDirectories);
167 	cache->searchableConfigDirectories = NULL;
168 }
169 
xdgWipeHandle(xdgHandle * handle)170 void xdgWipeHandle(xdgHandle *handle)
171 {
172 	xdgCachedData* cache = xdgGetCache(handle);
173 	xdgFreeData(cache);
174 	free(cache);
175 }
176 
177 /** Get value for environment variable $name, defaulting to "defaultValue".
178  *	@param name Name of environment variable.
179  *	@param defaultValue Value to assume for environment variable if it is
180  *		unset or empty.
181  */
xdgGetEnv(const char * name,const char * defaultValue)182 static char* xdgGetEnv(const char* name, const char* defaultValue)
183 {
184 	const char* env;
185 	char* value;
186 
187 	env = getenv(name);
188 	if (env && env[0])
189 	{
190 		if (!(value = (char*)malloc(strlen(env)+1))) return NULL;
191 		strcpy(value, env);
192 	}
193 	else
194 	{
195 		if (!(value = (char*)malloc(strlen(defaultValue)+1))) return NULL;
196 		strcpy(value, defaultValue);
197 	}
198 	return value;
199 }
200 
201 /** Split string at ':', return null-terminated list of resulting strings.
202  * @param string String to be split
203  */
xdgSplitPath(const char * string)204 static char** xdgSplitPath(const char* string)
205 {
206 	unsigned int size, i, j, k;
207 	char** itemlist;
208 
209 	/* Get the number of paths */
210 	size=2; /* One item more than seperators + terminating null item */
211 	for (i = 0; string[i]; ++i)
212 	{
213 #ifndef NO_ESCAPES_IN_PATHS
214 		if (string[i] == '\\' && string[i+1])
215 		{
216 			/* skip escaped characters including seperators */
217 			++i;
218 			continue;
219 		}
220 #endif
221 		if (string[i] == PATH_SEPARATOR_CHAR) ++size;
222 	}
223 
224 	if (!(itemlist = (char**)malloc(sizeof(char*)*size))) return NULL;
225 	xdgZeroMemory(itemlist, sizeof(char*)*size);
226 
227 	for (i = 0; *string; ++i)
228 	{
229 		/* get length of current string  */
230 		for (j = 0; string[j] && string[j] != PATH_SEPARATOR_CHAR; ++j)
231 #ifndef NO_ESCAPES_IN_PATHS
232 			if (string[j] == '\\' && string[j+1]) ++j
233 #endif
234 			;
235 
236 		if (!(itemlist[i] = (char*)malloc(j+1))) { xdgFreeStringList(itemlist); return NULL; }
237 
238 		/* transfer string, unescaping any escaped seperators */
239 		for (k = j = 0; string[j] && string[j] != PATH_SEPARATOR_CHAR; ++j, ++k)
240 		{
241 #ifndef NO_ESCAPES_IN_PATHS
242 			if (string[j] == '\\' && string[j+1] == PATH_SEPARATOR_CHAR) ++j; /* replace escaped ':' with just ':' */
243 			else if (string[j] == '\\' && string[j+1]) /* skip escaped characters so escaping remains aligned to pairs. */
244 			{
245 				itemlist[i][k]=string[j];
246 				++j, ++k;
247 			}
248 #endif
249 			itemlist[i][k] = string[j];
250 		}
251 		itemlist[i][k] = 0; /* Bugfix provided by Diego 'Flameeyes' Pettenò */
252 		/* move to next string */
253 		string += j;
254 		if (*string == PATH_SEPARATOR_CHAR) string++; /* skip seperator */
255 	}
256 	return itemlist;
257 }
258 
259 /** Get $PATH-style environment variable as list of strings.
260  * If $name is unset or empty, use default strings specified by variable arguments.
261  * @param name Name of environment variable
262  * @param strings NULL-terminated list of strings to be copied and used as defaults
263  */
xdgGetPathListEnv(const char * name,const char ** strings)264 static char** xdgGetPathListEnv(const char* name, const char ** strings)
265 {
266 	const char* env;
267 	char* item;
268 	char** itemlist;
269 	int i, size;
270 
271 	env = getenv(name);
272 	if (env && env[0])
273 	{
274 		if (!(item = (char*)malloc(strlen(env)+1))) return NULL;
275 		strcpy(item, env);
276 
277 		itemlist = xdgSplitPath(item);
278 		free(item);
279 	}
280 	else
281 	{
282 		if (!strings) return NULL;
283 		for (size = 0; strings[size]; ++size) ;
284                 ++size;
285 		if (!(itemlist = (char**)malloc(sizeof(char*)*size))) return NULL;
286 		xdgZeroMemory(itemlist, sizeof(char*)*(size));
287 
288 		/* Copy defaults into itemlist. */
289 		/* Why all this funky stuff? So the result can be handled uniformly by xdgFreeStringList. */
290 		for (i = 0; strings[i]; ++i)
291 		{
292 			if (!(item = (char*)malloc(strlen(strings[i])+1))) { xdgFreeStringList(itemlist); return NULL; }
293 			strcpy(item, strings[i]);
294 			itemlist[i] = item;
295 		}
296 	}
297 	return itemlist;
298 }
299 
300 /** Update all *Home variables of cache.
301  * This includes xdgCachedData::dataHome, xdgCachedData::configHome and xdgCachedData::cacheHome.
302  * @param cache Data cache to be updated
303  */
xdgUpdateHomeDirectories(xdgCachedData * cache)304 static int xdgUpdateHomeDirectories(xdgCachedData* cache)
305 {
306 	const char* env;
307 	char* home, *defVal = NULL;
308         int result = FALSE;
309 
310 	env = getenv("HOME");
311 	if (!env || !env[0])
312 		return FALSE;
313 	if (!(home = (char*)malloc(strlen(env)+1))) goto out;
314 	strcpy(home, env);
315 
316 	/* Allocate maximum needed for any of the 3 default values */
317 	defVal = (char*)malloc(strlen(home)+
318 		MAX(MAX(sizeof(DefaultRelativeDataHome), sizeof(DefaultRelativeConfigHome)), sizeof(DefaultRelativeCacheHome)));
319 	if (!defVal) goto out;
320 
321 	strcpy(defVal, home);
322 	strcat(defVal, DefaultRelativeDataHome);
323 	if (!(cache->dataHome = xdgGetEnv("XDG_DATA_HOME", defVal))) goto out;
324 
325 	defVal[strlen(home)] = 0;
326 	strcat(defVal, DefaultRelativeConfigHome);
327 	if (!(cache->configHome = xdgGetEnv("XDG_CONFIG_HOME", defVal))) goto out;
328 
329 	defVal[strlen(home)] = 0;
330 	strcat(defVal, DefaultRelativeCacheHome);
331 	if (!(cache->cacheHome = xdgGetEnv("XDG_CACHE_HOME", defVal))) goto out;
332 
333         result = TRUE;
334  out:
335 	free(defVal);
336 	free(home);
337 
338 	return result;
339 }
340 
341 /** Update all *Directories variables of cache.
342  * This includes xdgCachedData::searchableDataDirectories and xdgCachedData::searchableConfigDirectories.
343  * @param cache Data cache to be updated.
344  */
xdgUpdateDirectoryLists(xdgCachedData * cache)345 static int xdgUpdateDirectoryLists(xdgCachedData* cache)
346 {
347 	char** itemlist;
348 	int size;
349 
350 	itemlist = xdgGetPathListEnv("XDG_DATA_DIRS", DefaultDataDirectoriesList);
351 
352 	if (!itemlist) return FALSE;
353 	for (size = 0; itemlist[size]; size++) ; /* Get list size */
354 	if (!(cache->searchableDataDirectories = (char**)malloc(sizeof(char*)*(size+2))))
355 	{
356 		xdgFreeStringList(itemlist);
357 		return FALSE;
358 	}
359 	/* "home" directory has highest priority according to spec */
360 	cache->searchableDataDirectories[0] = cache->dataHome;
361 	memcpy(&(cache->searchableDataDirectories[1]), itemlist, sizeof(char*)*(size+1));
362 	free(itemlist);
363 
364 	itemlist = xdgGetPathListEnv("XDG_CONFIG_DIRS", DefaultConfigDirectoriesList);
365 	if (!itemlist) return FALSE;
366 	for (size = 0; itemlist[size]; size++) ; /* Get list size */
367 	if (!(cache->searchableConfigDirectories = (char**)malloc(sizeof(char*)*(size+2))))
368 	{
369 		xdgFreeStringList(itemlist);
370 		return FALSE;
371 	}
372 	cache->searchableConfigDirectories[0] = cache->configHome;
373 	memcpy(&(cache->searchableConfigDirectories[1]), itemlist, sizeof(char*)*(size+1));
374 	free(itemlist);
375 
376 	return TRUE;
377 }
378 
xdgUpdateData(xdgHandle * handle)379 int xdgUpdateData(xdgHandle *handle)
380 {
381 	xdgCachedData* cache = (xdgCachedData*)malloc(sizeof(xdgCachedData));
382 	xdgCachedData* oldCache;
383 	if (!cache) return FALSE;
384 	xdgZeroMemory(cache, sizeof(xdgCachedData));
385 
386 	if (xdgUpdateHomeDirectories(cache) &&
387 		xdgUpdateDirectoryLists(cache))
388 	{
389 		/* Update successful, replace pointer to old cache with pointer to new cache */
390 		oldCache = xdgGetCache(handle);
391 		handle->reserved = cache;
392 		if (oldCache)
393 		{
394 			xdgFreeData(oldCache);
395 			free(oldCache);
396 		}
397 		return TRUE;
398 	}
399 	else
400 	{
401 		/* Update failed, discard new cache and leave old cache unmodified */
402 		xdgFreeData(cache);
403 		free(cache);
404 		return FALSE;
405 	}
406 }
407 
408 /** Find all existing files corresponding to relativePath relative to each item in dirList.
409   * @param relativePath Relative path to search for.
410   * @param dirList <tt>NULL</tt>-terminated list of directory paths.
411   * @return A sequence of null-terminated strings terminated by a
412   * 	double-<tt>NULL</tt> (empty string) and allocated using malloc().
413   */
xdgFindExisting(const char * relativePath,const char * const * dirList)414 static char * xdgFindExisting(const char * relativePath, const char * const * dirList)
415 {
416 	char * fullPath;
417 	char * returnString = 0;
418 	char * tmpString;
419 	int strLen = 0;
420 	FILE * testFile;
421 	const char * const * item;
422 
423 	for (item = dirList; *item; item++)
424 	{
425 		if (!(fullPath = (char*)malloc(strlen(*item)+strlen(relativePath)+2)))
426 		{
427 			if (returnString) free(returnString);
428 			return NULL;
429 		}
430 		strcpy(fullPath, *item);
431 		if (fullPath[strlen(fullPath)-1] != DIR_SEPARATOR_CHAR)
432 			strcat(fullPath, DIR_SEPARATOR_STR);
433 		strcat(fullPath, relativePath);
434 		testFile = fopen(fullPath, "r");
435 		if (testFile)
436 		{
437 			fclose(testFile);
438 			if (!(tmpString = (char*)realloc(returnString, strLen+strlen(fullPath)+2)))
439 			{
440 				free(returnString);
441 				free(fullPath);
442 				return NULL;
443 			}
444 			returnString = tmpString;
445 			strcpy(&returnString[strLen], fullPath);
446 			strLen = strLen+strlen(fullPath)+1;
447 		}
448 		free(fullPath);
449 	}
450 	if (returnString)
451 		returnString[strLen] = 0;
452 	else
453 	{
454 		if ((returnString = (char*)malloc(2)))
455 			strcpy(returnString, "\0");
456 	}
457 	return returnString;
458 }
459 
460 /** Open first possible config file corresponding to relativePath.
461   * @param relativePath Path to scan for.
462   * @param mode Mode with which to attempt to open files (see fopen modes).
463   * @param dirList <tt>NULL</tt>-terminated list of paths in which to search for relativePath.
464   * @return File pointer if successful else @c NULL. Client must use @c fclose to close file.
465   */
xdgFileOpen(const char * relativePath,const char * mode,const char * const * dirList)466 static FILE * xdgFileOpen(const char * relativePath, const char * mode, const char * const * dirList)
467 {
468 	char * fullPath;
469 	FILE * testFile;
470 	const char * const * item;
471 
472 	for (item = dirList; *item; item++)
473 	{
474 		if (!(fullPath = (char*)malloc(strlen(*item)+strlen(relativePath)+2)))
475 			return NULL;
476 		strcpy(fullPath, *item);
477 		if (fullPath[strlen(fullPath)-1] != DIR_SEPARATOR_CHAR)
478 			strcat(fullPath, DIR_SEPARATOR_STR);
479 		strcat(fullPath, relativePath);
480 		testFile = fopen(fullPath, mode);
481 		free(fullPath);
482 		if (testFile)
483 			return testFile;
484 	}
485 	return NULL;
486 }
487 
xdgMakePath(const char * path,mode_t mode)488 int xdgMakePath(const char * path, mode_t mode)
489 {
490 	int length = strlen(path);
491 	char * tmpPath;
492 	char * tmpPtr;
493 	int ret;
494 
495 	if (length == 0 || (length == 1 && path[0] == DIR_SEPARATOR_CHAR))
496 		return 0;
497 
498 	if (!(tmpPath = (char*)malloc(length+1)))
499 	{
500 		errno = ENOMEM;
501 		return -1;
502 	}
503 	strcpy(tmpPath, path);
504 	if (tmpPath[length-1] == DIR_SEPARATOR_CHAR)
505 		tmpPath[length-1] = '\0';
506 
507 	/* skip tmpPath[0] since if it's a seperator we have an absolute path */
508 	for (tmpPtr = tmpPath+1; *tmpPtr; ++tmpPtr)
509 	{
510 		if (*tmpPtr == DIR_SEPARATOR_CHAR)
511 		{
512 			*tmpPtr = '\0';
513 			if (mkdir(tmpPath, mode) == -1)
514 			{
515 				if (errno != EEXIST)
516 				{
517 					free(tmpPath);
518 					return -1;
519 				}
520 			}
521 			*tmpPtr = DIR_SEPARATOR_CHAR;
522 		}
523 	}
524 	ret = mkdir(tmpPath, mode);
525 	free(tmpPath);
526 	return ret;
527 }
528 
xdgDataHome(xdgHandle * handle)529 const char * xdgDataHome(xdgHandle *handle)
530 {
531 	return xdgGetCache(handle)->dataHome;
532 }
xdgConfigHome(xdgHandle * handle)533 const char * xdgConfigHome(xdgHandle *handle)
534 {
535 	return xdgGetCache(handle)->configHome;
536 }
xdgDataDirectories(xdgHandle * handle)537 const char * const * xdgDataDirectories(xdgHandle *handle)
538 {
539 	return (const char * const *)&(xdgGetCache(handle)->searchableDataDirectories[1]);
540 }
xdgSearchableDataDirectories(xdgHandle * handle)541 const char * const * xdgSearchableDataDirectories(xdgHandle *handle)
542 {
543 	return (const char * const *)xdgGetCache(handle)->searchableDataDirectories;
544 }
xdgConfigDirectories(xdgHandle * handle)545 const char * const * xdgConfigDirectories(xdgHandle *handle)
546 {
547 	return (const char * const *)&(xdgGetCache(handle)->searchableConfigDirectories[1]);
548 }
xdgSearchableConfigDirectories(xdgHandle * handle)549 const char * const * xdgSearchableConfigDirectories(xdgHandle *handle)
550 {
551 	return (const char * const *)xdgGetCache(handle)->searchableConfigDirectories;
552 }
xdgCacheHome(xdgHandle * handle)553 const char * xdgCacheHome(xdgHandle *handle)
554 {
555 	return xdgGetCache(handle)->cacheHome;
556 }
xdgDataFind(const char * relativePath,xdgHandle * handle)557 char * xdgDataFind(const char * relativePath, xdgHandle *handle)
558 {
559 	return xdgFindExisting(relativePath, xdgSearchableDataDirectories(handle));
560 }
xdgConfigFind(const char * relativePath,xdgHandle * handle)561 char * xdgConfigFind(const char * relativePath, xdgHandle *handle)
562 {
563 	return xdgFindExisting(relativePath, xdgSearchableConfigDirectories(handle));
564 }
xdgDataOpen(const char * relativePath,const char * mode,xdgHandle * handle)565 FILE * xdgDataOpen(const char * relativePath, const char * mode, xdgHandle *handle)
566 {
567 	return xdgFileOpen(relativePath, mode, xdgSearchableDataDirectories(handle));
568 }
xdgConfigOpen(const char * relativePath,const char * mode,xdgHandle * handle)569 FILE * xdgConfigOpen(const char * relativePath, const char * mode, xdgHandle *handle)
570 {
571 	return xdgFileOpen(relativePath, mode, xdgSearchableConfigDirectories(handle));
572 }
573 
574