1 /**
2  * Functions for retrieving standard paths in cross-platform manner.
3  * Authors: 
4  *  $(LINK2 https://github.com/MyLittleRobo, Roman Chistokhodov)
5  * License: 
6  *  $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
7  * Copyright:
8  *  Roman Chistokhodov 2015
9  */
10 
11 module standardpaths;
12 
13 private {
14     import std.process : environment;
15     import std.array;
16     import std.path;
17     import std.file;
18     import std.algorithm : splitter, canFind, filter;
19     import std.exception;
20     import std.range;
21     
22     import isfreedesktop;
23     
24     debug {
25         import std.stdio : stderr;
26     }
27     
28     static if( __VERSION__ < 2066 ) enum nogc = 1;
29 }
30 
31 version(Windows) {
32     private {
33         static if (__VERSION__ < 2070) {
34             import std.c.windows.windows;
35         } else {
36             import core.sys.windows.windows;
37         }
38         
39         import std.utf;
40         import std.uni : toLower, sicmp;
41     }
42 } else version(Posix) {
43     private {
44         import std.stdio : File, StdioException;
45         import std.string : toStringz;
46         
47         static if (is(typeof({import std.string : fromStringz;}))) {
48             import std.string : fromStringz;
49         } else { //own fromStringz implementation for compatibility reasons
50             import std.c.string : strlen;
51             @system pure inout(char)[] fromStringz(inout(char)* cString) {
52                 return cString ? cString[0..strlen(cString)] : null;
53             }
54         }
55         
56         string verifyIfNeeded(string path, bool shouldVerify) nothrow @trusted
57         {
58             if (path.length && shouldVerify) {
59                 bool dirExists;
60                 collectException(path.isDir, dirExists);
61                 return dirExists ? path : null;
62             } else {
63                 return path;
64             }
65         }
66     }
67 } else {
68     static assert(false, "Unsupported platform");
69 }
70 
71 /** 
72  * Locations that can be passed to writablePath and standardPaths functions.
73  * See_Also:
74  *  writablePath, standardPaths
75  */
76 enum StandardPath {
77     /**
78      * General location of persisted application data. Every application should have its own subdirectory here.
79      * Note: on Windows it's the same as $(B config) path.
80      */
81     data,
82     /**
83      * General location of configuration files. Every application should have its own subdirectory here.
84      * Note: on Windows it's the same as $(B data) path.
85      */
86     config,
87     /**
88      * Location of cached data.
89      * Note: Not available on Windows.
90      */
91     cache,
92     ///User's desktop directory.
93     desktop,
94     ///User's documents.
95     documents,
96     ///User's pictures.
97     pictures,
98     
99     ///User's music.
100     music,
101     
102     ///User's videos (movies).
103     videos,
104     
105     ///Directory for user's downloaded files.
106     downloads,
107     
108     /**
109      * Location of file templates (e.g. office suite document templates).
110      * Note: Not available on OS X.
111      */
112     templates,
113     
114     /** 
115      * Public share folder.
116      * Note: Not available on Windows.
117      */
118     publicShare,
119     /**
120      * Location of fonts files.
121      * Note: don't rely on this on freedesktop, since it uses hardcoded paths there. Better consider using $(LINK2 http://www.freedesktop.org/wiki/Software/fontconfig/, fontconfig library)
122      */
123     fonts,
124     /**
125      * User's applications. This has different meaning across platforms.
126      * On Windows it's directory where links to programs for Start menu are stored.
127      * On OS X it's folder where applications are typically put.
128      * On Freedesktop it's directory where .desktop files are put.
129      */
130     applications,
131     
132     /**
133      * Automatically started applications.
134      */
135     startup
136 }
137 
138 /**
139  * Control behavior of functions.
140  * See_Also: writablePath
141  */
142 enum FolderFlag
143 {
144     none = 0,   /// Don't verify that folder exist.
145     create = 1, /// Create if folder does not exist.
146     verify = 2  /// Verify that folder exists.
147 }
148 
149 /**
150  * Current user home directory.
151  * Returns: Path to user home directory, or an empty string if could not determine home directory.
152  * Relies on environment variables.
153  * Note: This function does not cache its result.
154  */
155 string homeDir() nothrow @safe
156 {
157     try {
158         version(Windows) {
159             //Use GetUserProfileDirectoryW from Userenv.dll?
160             string home = environment.get("USERPROFILE");
161             if (home.empty) {
162                 string homeDrive = environment.get("HOMEDRIVE");
163                 string homePath = environment.get("HOMEPATH");
164                 if (homeDrive.length && homePath.length) {
165                     home = homeDrive ~ homePath;
166                 }
167             }
168             return home;
169         } else {
170             string home = environment.get("HOME");
171             return home;
172         }
173     }
174     catch (Exception e) {
175         debug {
176             @trusted void writeException(Exception e) nothrow {
177                 collectException(stderr.writefln("Error when getting home directory %s", e.msg));
178             }
179             writeException(e);
180         }
181         return null;
182     }
183 }
184 
185 /**
186  * Getting writable paths for various locations.
187  * Returns: Path where files of $(U type) should be written to by current user, or an empty string if could not determine path.
188  * Params:
189  *  type = Directory to lookup.
190  *  params = Union of $(B FolderFlag)s.
191  * Note: This function does not cache its results.
192  * See_Also: FolderFlag
193  */
194 string writablePath(StandardPath type, FolderFlag params = FolderFlag.none) nothrow @safe;
195 
196 /**
197  * Getting paths for various locations.
198  * Returns: Array of paths where files of $(U type) belong including one returned by $(B writablePath), or an empty array if no paths are defined for $(U type).
199  * This function does not ensure if all returned paths exist and appear to be accessible directories. Returned strings are not required to be unique.
200  * Note: This function does cache its results. 
201  * It may cause performance impact to call this function often since retrieving some paths can be relatively expensive operation.
202  * See_Also:
203  *  writablePath
204  */
205 string[] standardPaths(StandardPath type) nothrow @safe;
206 
207 
208 version(StandardPathsDocs)
209 {
210     /**
211      * Path to runtime user directory on freedesktop platforms.
212      * Returns: User's runtime directory determined by $(B XDG_RUNTIME_DIR) environment variable. 
213      * If directory does not exist it tries to create one with appropriate permissions. On fail returns an empty string.
214      * Note: Deprecated, use xdgRuntimeDir instead.
215      */
216     deprecated string runtimeDir() nothrow @trusted;
217     
218     /**
219      * Path to $(B Roaming) data directory. 
220      * Returns: User's Roaming directory. On fail returns an empty string.
221      * Note: This function is Windows only.
222      */
223     string roamingPath(bool shouldCreate = false) nothrow @safe;
224     
225     /**
226      * Location where games may store their saves. 
227      * This is common path for games. One should use subfolder for their game saves.
228      * Returns: User's Saved Games directory. On fail returns an empty string.
229      * Note: This function is Windows only.
230      */
231     string savedGames(bool shouldCreate = false) nothrow @safe;
232 }
233 
234 version(Windows) {
235     private enum pathVarSeparator = ';';
236 } else version(Posix) {
237     private enum pathVarSeparator = ':';
238 }
239 
240 version(Windows) {
241     
242     private {
243         enum {
244             CSIDL_DESKTOP            =  0,
245             CSIDL_INTERNET,
246             CSIDL_PROGRAMS,
247             CSIDL_CONTROLS,
248             CSIDL_PRINTERS,
249             CSIDL_PERSONAL,
250             CSIDL_FAVORITES,
251             CSIDL_STARTUP,
252             CSIDL_RECENT,
253             CSIDL_SENDTO,
254             CSIDL_BITBUCKET,
255             CSIDL_STARTMENU,      // = 11
256             CSIDL_MYMUSIC            = 13,
257             CSIDL_MYVIDEO,        // = 14
258             CSIDL_DESKTOPDIRECTORY   = 16,
259             CSIDL_DRIVES,
260             CSIDL_NETWORK,
261             CSIDL_NETHOOD,
262             CSIDL_FONTS,
263             CSIDL_TEMPLATES,
264             CSIDL_COMMON_STARTMENU,
265             CSIDL_COMMON_PROGRAMS,
266             CSIDL_COMMON_STARTUP,
267             CSIDL_COMMON_DESKTOPDIRECTORY,
268             CSIDL_APPDATA,
269             CSIDL_PRINTHOOD,
270             CSIDL_LOCAL_APPDATA,
271             CSIDL_ALTSTARTUP,
272             CSIDL_COMMON_ALTSTARTUP,
273             CSIDL_COMMON_FAVORITES,
274             CSIDL_INTERNET_CACHE,
275             CSIDL_COOKIES,
276             CSIDL_HISTORY,
277             CSIDL_COMMON_APPDATA,
278             CSIDL_WINDOWS,
279             CSIDL_SYSTEM,
280             CSIDL_PROGRAM_FILES,
281             CSIDL_MYPICTURES,
282             CSIDL_PROFILE,
283             CSIDL_SYSTEMX86,
284             CSIDL_PROGRAM_FILESX86,
285             CSIDL_PROGRAM_FILES_COMMON,
286             CSIDL_PROGRAM_FILES_COMMONX86,
287             CSIDL_COMMON_TEMPLATES,
288             CSIDL_COMMON_DOCUMENTS,
289             CSIDL_COMMON_ADMINTOOLS,
290             CSIDL_ADMINTOOLS,
291             CSIDL_CONNECTIONS,  // = 49
292             CSIDL_COMMON_MUSIC     = 53,
293             CSIDL_COMMON_PICTURES,
294             CSIDL_COMMON_VIDEO,
295             CSIDL_RESOURCES,
296             CSIDL_RESOURCES_LOCALIZED,
297             CSIDL_COMMON_OEM_LINKS,
298             CSIDL_CDBURN_AREA,  // = 59
299             CSIDL_COMPUTERSNEARME  = 61,
300             CSIDL_FLAG_DONT_VERIFY = 0x4000,
301             CSIDL_FLAG_CREATE      = 0x8000,
302             CSIDL_FLAG_MASK        = 0xFF00
303         }
304         
305         enum {
306             KF_FLAG_SIMPLE_IDLIST                = 0x00000100,
307             KF_FLAG_NOT_PARENT_RELATIVE          = 0x00000200,
308             KF_FLAG_DEFAULT_PATH                 = 0x00000400,
309             KF_FLAG_INIT                         = 0x00000800,
310             KF_FLAG_NO_ALIAS                     = 0x00001000,
311             KF_FLAG_DONT_UNEXPAND                = 0x00002000,
312             KF_FLAG_DONT_VERIFY                  = 0x00004000,
313             KF_FLAG_CREATE                       = 0x00008000,
314             KF_FLAG_NO_APPCONTAINER_REDIRECTION  = 0x00010000,
315             KF_FLAG_ALIAS_ONLY                   = 0x80000000
316         };
317         
318         alias GUID KNOWNFOLDERID;
319         
320         enum KNOWNFOLDERID FOLDERID_LocalAppData = {0xf1b32785, 0x6fba, 0x4fcf, [0x9d,0x55,0x7b,0x8e,0x7f,0x15,0x70,0x91]};
321         enum KNOWNFOLDERID FOLDERID_RoamingAppData = {0x3eb685db, 0x65f9, 0x4cf6, [0xa0,0x3a,0xe3,0xef,0x65,0x72,0x9f,0x3d]};
322 
323         enum KNOWNFOLDERID FOLDERID_Desktop = {0xb4bfcc3a, 0xdb2c, 0x424c, [0xb0,0x29,0x7f,0xe9,0x9a,0x87,0xc6,0x41]};
324         enum KNOWNFOLDERID FOLDERID_Documents = {0xfdd39ad0, 0x238f, 0x46af, [0xad,0xb4,0x6c,0x85,0x48,0x3,0x69,0xc7]};
325         enum KNOWNFOLDERID FOLDERID_Downloads = {0x374de290, 0x123f, 0x4565, [0x91,0x64,0x39,0xc4,0x92,0x5e,0x46,0x7b]};
326         enum KNOWNFOLDERID FOLDERID_Favorites = {0x1777f761, 0x68ad, 0x4d8a, [0x87,0xbd,0x30,0xb7,0x59,0xfa,0x33,0xdd]};
327         enum KNOWNFOLDERID FOLDERID_Links = {0xbfb9d5e0, 0xc6a9, 0x404c, [0xb2,0xb2,0xae,0x6d,0xb6,0xaf,0x49,0x68]};
328         enum KNOWNFOLDERID FOLDERID_Music = {0x4bd8d571, 0x6d19, 0x48d3, [0xbe,0x97,0x42,0x22,0x20,0x8,0xe,0x43]};
329         enum KNOWNFOLDERID FOLDERID_Pictures = {0x33e28130, 0x4e1e, 0x4676, [0x83,0x5a,0x98,0x39,0x5c,0x3b,0xc3,0xbb]};
330         enum KNOWNFOLDERID FOLDERID_Programs = {0xa77f5d77, 0x2e2b, 0x44c3, [0xa6,0xa2,0xab,0xa6,0x1,0x5,0x4a,0x51]};
331         enum KNOWNFOLDERID FOLDERID_SavedGames = {0x4c5c32ff, 0xbb9d, 0x43b0, [0xb5,0xb4,0x2d,0x72,0xe5,0x4e,0xaa,0xa4]};
332         enum KNOWNFOLDERID FOLDERID_Startup = {0xb97d20bb, 0xf46a, 0x4c97, [0xba,0x10,0x5e,0x36,0x8,0x43,0x8,0x54]};
333         enum KNOWNFOLDERID FOLDERID_Templates = {0xa63293e8, 0x664e, 0x48db, [0xa0,0x79,0xdf,0x75,0x9e,0x5,0x9,0xf7]};
334         enum KNOWNFOLDERID FOLDERID_Videos = {0x18989b1d, 0x99b5, 0x455b, [0x84,0x1c,0xab,0x7c,0x74,0xe4,0xdd,0xfc]};
335 
336         enum KNOWNFOLDERID FOLDERID_Fonts = {0xfd228cb7, 0xae11, 0x4ae3, [0x86,0x4c,0x16,0xf3,0x91,0xa,0xb8,0xfe]};
337         enum KNOWNFOLDERID FOLDERID_ProgramData = {0x62ab5d82, 0xfdc1, 0x4dc3, [0xa9,0xdd,0x7,0xd,0x1d,0x49,0x5d,0x97]};
338         enum KNOWNFOLDERID FOLDERID_CommonPrograms = {0x139d44e, 0x6afe, 0x49f2, [0x86,0x90,0x3d,0xaf,0xca,0xe6,0xff,0xb8]};
339         enum KNOWNFOLDERID FOLDERID_CommonStartup = {0x82a5ea35, 0xd9cd, 0x47c5, [0x96,0x29,0xe1,0x5d,0x2f,0x71,0x4e,0x6e]};
340         enum KNOWNFOLDERID FOLDERID_CommonTemplates = {0xb94237e7, 0x57ac, 0x4347, [0x91,0x51,0xb0,0x8c,0x6c,0x32,0xd1,0xf7]};
341 
342         enum KNOWNFOLDERID FOLDERID_PublicDesktop = {0xc4aa340d, 0xf20f, 0x4863, [0xaf,0xef,0xf8,0x7e,0xf2,0xe6,0xba,0x25]};
343         enum KNOWNFOLDERID FOLDERID_PublicDocuments = {0xed4824af, 0xdce4, 0x45a8, [0x81,0xe2,0xfc,0x79,0x65,0x8,0x36,0x34]};
344         enum KNOWNFOLDERID FOLDERID_PublicDownloads = {0x3d644c9b, 0x1fb8, 0x4f30, [0x9b,0x45,0xf6,0x70,0x23,0x5f,0x79,0xc0]};
345         enum KNOWNFOLDERID FOLDERID_PublicMusic = {0x3214fab5, 0x9757, 0x4298, [0xbb,0x61,0x92,0xa9,0xde,0xaa,0x44,0xff]};
346         enum KNOWNFOLDERID FOLDERID_PublicPictures = {0xb6ebfb86, 0x6907, 0x413c, [0x9a,0xf7,0x4f,0xc2,0xab,0xf0,0x7c,0xc5]};
347         enum KNOWNFOLDERID FOLDERID_PublicVideos = {0x2400183a, 0x6185, 0x49fb, [0xa2,0xd8,0x4a,0x39,0x2a,0x60,0x2b,0xa3]};
348     }
349     
350     private  {
351         extern(Windows) @nogc @system BOOL _dummy_SHGetSpecialFolderPath(HWND, wchar*, int, BOOL) nothrow { return 0; }
352         extern(Windows) @nogc @system HRESULT _dummy_SHGetKnownFolderPath(const(KNOWNFOLDERID)* rfid, DWORD dwFlags, HANDLE hToken, wchar** ppszPath) nothrow { return 0; }
353         extern(Windows) @nogc @system void _dummy_CoTaskMemFree(void* pv) nothrow {return;}
354         
355         __gshared typeof(&_dummy_SHGetSpecialFolderPath) ptrSHGetSpecialFolderPath = null;
356         __gshared typeof(&_dummy_SHGetKnownFolderPath) ptrSHGetKnownFolderPath = null;
357         __gshared typeof(&_dummy_CoTaskMemFree) ptrCoTaskMemFree = null;
358         
359         @nogc @trusted bool hasSHGetSpecialFolderPath() nothrow {
360             return ptrSHGetSpecialFolderPath !is null;
361         }
362         
363         @nogc @trusted bool hasSHGetKnownFolderPath() nothrow {
364             return ptrSHGetKnownFolderPath !is null && ptrCoTaskMemFree !is null;
365         }
366     }
367     
368     shared static this() 
369     {
370         HMODULE shellLib = LoadLibraryA("Shell32");
371         if (shellLib !is null) {
372             ptrSHGetKnownFolderPath = cast(typeof(ptrSHGetKnownFolderPath))enforce(GetProcAddress(shellLib, "SHGetKnownFolderPath"));
373             if (ptrSHGetKnownFolderPath) {
374                 HMODULE ole = LoadLibraryA("Ole32");
375                 if (ole !is null) {
376                     ptrCoTaskMemFree = cast(typeof(ptrCoTaskMemFree))enforce(GetProcAddress(ole, "CoTaskMemFree"));
377                     if (!ptrCoTaskMemFree) {
378                         FreeLibrary(ole);
379                     }
380                 }
381             }
382             
383             if (!hasSHGetKnownFolderPath()) {
384                 ptrSHGetSpecialFolderPath = cast(typeof(ptrSHGetSpecialFolderPath))GetProcAddress(shellLib, "SHGetSpecialFolderPathW");
385             }
386         }
387     }
388     
389     private string getCSIDLFolder(int csidl, FolderFlag params = FolderFlag.none) nothrow @trusted {
390         import core.stdc.wchar_ : wcslen;
391         
392         if (params & FolderFlag.create) {
393             csidl |= CSIDL_FLAG_CREATE;
394         }
395         if (!(params & FolderFlag.verify)) {
396             csidl |= CSIDL_FLAG_DONT_VERIFY;
397         }
398         wchar[MAX_PATH] path = void;
399         if (hasSHGetSpecialFolderPath() && ptrSHGetSpecialFolderPath(null, path.ptr, csidl, FALSE)) {
400             size_t len = wcslen(path.ptr);
401             try {
402                 return toUTF8(path[0..len]);
403             } catch(Exception e) {
404                 
405             }
406         }
407         return null;
408     }
409     
410     private string getKnownFolder(const(KNOWNFOLDERID) folder, FolderFlag params = FolderFlag.none) nothrow @trusted {
411         import core.stdc.wchar_ : wcslen;
412         
413         wchar* str;
414         
415         DWORD flags = 0;
416         if (params & FolderFlag.create) {
417             flags |= KF_FLAG_CREATE;
418         }
419         if (!(params & FolderFlag.verify)) {
420             flags |= KF_FLAG_DONT_VERIFY;
421         }
422         
423         if (hasSHGetKnownFolderPath() && ptrSHGetKnownFolderPath(&folder, flags, null, &str) == S_OK) {
424             scope(exit) ptrCoTaskMemFree(str);
425             try {
426                 return str[0..wcslen(str)].toUTF8;
427             } catch(Exception e) {
428                 
429             }
430         }
431         return null;
432     }
433     
434     string roamingPath(FolderFlag params = FolderFlag.none) nothrow @safe
435     {
436         if (hasSHGetKnownFolderPath()) {
437             return getKnownFolder(FOLDERID_RoamingAppData, params);
438         } else if (hasSHGetSpecialFolderPath()) {
439             return getCSIDLFolder(CSIDL_APPDATA, params);
440         } else {
441             return null;
442         }
443     }
444     
445     string savedGames(FolderFlag params = FolderFlag.none) nothrow @safe
446     {
447         if (hasSHGetKnownFolderPath()) {
448             return getKnownFolder(FOLDERID_SavedGames, params);
449         } else {
450             return null;
451         }
452     }
453     
454     string writablePath(StandardPath type, FolderFlag params = FolderFlag.none) nothrow @safe
455     {
456         if (hasSHGetKnownFolderPath()) {
457             final switch(type) {
458                 case StandardPath.config:
459                 case StandardPath.data:
460                     return getKnownFolder(FOLDERID_LocalAppData, params);
461                 case StandardPath.cache:
462                     return null;
463                 case StandardPath.desktop:
464                     return getKnownFolder(FOLDERID_Desktop, params);
465                 case StandardPath.documents:
466                     return getKnownFolder(FOLDERID_Documents, params);
467                 case StandardPath.pictures:
468                     return getKnownFolder(FOLDERID_Pictures, params);
469                 case StandardPath.music:
470                     return getKnownFolder(FOLDERID_Music, params);
471                 case StandardPath.videos:
472                     return getKnownFolder(FOLDERID_Videos, params);
473                 case StandardPath.downloads:
474                     return getKnownFolder(FOLDERID_Downloads, params);
475                 case StandardPath.templates:
476                     return getKnownFolder(FOLDERID_Templates, params);
477                 case StandardPath.publicShare:
478                     return null;
479                 case StandardPath.fonts:
480                     return null;
481                 case StandardPath.applications:
482                     return getKnownFolder(FOLDERID_Programs, params);
483                 case StandardPath.startup:
484                     return getKnownFolder(FOLDERID_Startup, params);
485             }
486         } else if (hasSHGetSpecialFolderPath()) {
487             final switch(type) {
488                 case StandardPath.config:
489                 case StandardPath.data:
490                     return getCSIDLFolder(CSIDL_LOCAL_APPDATA, params);
491                 case StandardPath.cache:
492                     return null;
493                 case StandardPath.desktop:
494                     return getCSIDLFolder(CSIDL_DESKTOPDIRECTORY, params);
495                 case StandardPath.documents:
496                     return getCSIDLFolder(CSIDL_PERSONAL, params);
497                 case StandardPath.pictures:
498                     return getCSIDLFolder(CSIDL_MYPICTURES, params);
499                 case StandardPath.music:
500                     return getCSIDLFolder(CSIDL_MYMUSIC, params);
501                 case StandardPath.videos:
502                     return getCSIDLFolder(CSIDL_MYVIDEO, params);
503                 case StandardPath.downloads:
504                     return null;
505                 case StandardPath.templates:
506                     return getCSIDLFolder(CSIDL_TEMPLATES, params);
507                 case StandardPath.publicShare:
508                     return null;
509                 case StandardPath.fonts:
510                     return null;
511                 case StandardPath.applications:
512                     return getCSIDLFolder(CSIDL_PROGRAMS, params);
513                 case StandardPath.startup:
514                     return getCSIDLFolder(CSIDL_STARTUP, params);
515             }
516         } else {
517             return null;
518         }
519     }
520     
521     string[] standardPaths(StandardPath type) nothrow @safe
522     {   
523         string commonPath;
524         
525         if (hasSHGetKnownFolderPath()) {
526             switch(type) {
527                 case StandardPath.config:
528                 case StandardPath.data:
529                     commonPath = getKnownFolder(FOLDERID_ProgramData);
530                     break;
531                 case StandardPath.desktop:
532                     commonPath = getKnownFolder(FOLDERID_PublicDesktop);
533                     break;
534                 case StandardPath.documents:
535                     commonPath = getKnownFolder(FOLDERID_PublicDocuments);
536                     break;
537                 case StandardPath.pictures:
538                     commonPath = getKnownFolder(FOLDERID_PublicPictures);
539                     break;
540                 case StandardPath.music:
541                     commonPath = getKnownFolder(FOLDERID_PublicMusic);
542                     break;
543                 case StandardPath.videos:
544                     commonPath = getKnownFolder(FOLDERID_PublicVideos);
545                     break;
546                 case StandardPath.downloads:
547                     commonPath = getKnownFolder(FOLDERID_PublicDownloads);
548                     break;
549                 case StandardPath.templates:
550                     commonPath = getKnownFolder(FOLDERID_CommonTemplates);
551                     break;
552                 case StandardPath.fonts:
553                     commonPath = getKnownFolder(FOLDERID_Fonts);
554                     break;
555                 case StandardPath.applications:
556                     commonPath = getKnownFolder(FOLDERID_CommonPrograms);
557                     break;
558                 case StandardPath.startup:
559                     commonPath = getKnownFolder(FOLDERID_CommonStartup);
560                     break;
561                 default:
562                     break;
563             }
564         } else if (hasSHGetSpecialFolderPath()) {
565             switch(type) {
566                 case StandardPath.config:
567                 case StandardPath.data:
568                     commonPath = getCSIDLFolder(CSIDL_COMMON_APPDATA);
569                     break;
570                 case StandardPath.desktop:
571                     commonPath = getCSIDLFolder(CSIDL_COMMON_DESKTOPDIRECTORY);
572                     break;
573                 case StandardPath.documents:
574                     commonPath = getCSIDLFolder(CSIDL_COMMON_DOCUMENTS);
575                     break;
576                 case StandardPath.pictures:
577                     commonPath = getCSIDLFolder(CSIDL_COMMON_PICTURES);
578                     break;
579                 case StandardPath.music:
580                     commonPath = getCSIDLFolder(CSIDL_COMMON_MUSIC);
581                     break;
582                 case StandardPath.videos:
583                     commonPath = getCSIDLFolder(CSIDL_COMMON_VIDEO);
584                     break;
585                 case StandardPath.templates:
586                     commonPath = getCSIDLFolder(CSIDL_COMMON_TEMPLATES);
587                     break;
588                 case StandardPath.fonts:
589                     commonPath = getCSIDLFolder(CSIDL_FONTS);
590                     break;
591                 case StandardPath.applications:
592                     commonPath = getCSIDLFolder(CSIDL_COMMON_PROGRAMS);
593                     break;
594                 case StandardPath.startup:
595                     commonPath = getCSIDLFolder(CSIDL_COMMON_STARTUP);
596                     break;
597                 default:
598                     break;
599             }
600         }
601         
602         string[] paths;
603         string userPath = writablePath(type);
604         if (userPath.length) 
605             paths ~= userPath;
606         if (commonPath.length)
607             paths ~= commonPath;
608         return paths;
609     }
610     
611     private const(string)[] executableExtensions() nothrow @trusted
612     {
613         static bool filenamesEqual(string first, string second) nothrow {
614             try {
615                 return filenameCmp(first, second) == 0;
616             } catch(Exception e) {
617                 return false;
618             }
619         }
620         
621         static string[] extensions;
622         if (extensions.empty) {
623             collectException(environment.get("PATHEXT").splitter(pathVarSeparator).array, extensions);
624             if (canFind!(filenamesEqual)(extensions, ".exe") == false) {
625                 extensions = [".exe", ".com", ".bat", ".cmd"];
626             }
627         }
628         
629         return extensions;
630     }
631 } else version(OSX) {
632     
633     private enum : short {
634         kOnSystemDisk                 = -32768L, /* previously was 0x8000 but that is an unsigned value whereas vRefNum is signed*/
635         kOnAppropriateDisk            = -32767, /* Generally, the same as kOnSystemDisk, but it's clearer that this isn't always the 'boot' disk.*/
636                                                 /* Folder Domains - Carbon only.  The constants above can continue to be used, but the folder/volume returned will*/
637                                                 /* be from one of the domains below.*/
638         kSystemDomain                 = -32766, /* Read-only system hierarchy.*/
639         kLocalDomain                  = -32765, /* All users of a single machine have access to these resources.*/
640         kNetworkDomain                = -32764, /* All users configured to use a common network server has access to these resources.*/
641         kUserDomain                   = -32763, /* Read/write. Resources that are private to the user.*/
642         kClassicDomain                = -32762, /* Domain referring to the currently configured Classic System Folder.  Not supported in Mac OS X Leopard and later.*/
643         kFolderManagerLastDomain      = -32760
644     }
645 
646     private @nogc int k(string s) nothrow {
647         return s[0] << 24 | s[1] << 16 | s[2] << 8 | s[3];
648     }
649 
650     private enum {
651         kDesktopFolderType            = k("desk"), /* the desktop folder; objects in this folder show on the desktop. */
652         kTrashFolderType              = k("trsh"), /* the trash folder; objects in this folder show up in the trash */
653         kWhereToEmptyTrashFolderType  = k("empt"), /* the "empty trash" folder; Finder starts empty from here down */
654         kFontsFolderType              = k("font"), /* Fonts go here */
655         kPreferencesFolderType        = k("pref"), /* preferences for applications go here */
656         kSystemPreferencesFolderType  = k("sprf"), /* the PreferencePanes folder, where Mac OS X Preference Panes go */
657         kTemporaryFolderType          = k("temp"), /*    On Mac OS X, each user has their own temporary items folder, and the Folder Manager attempts to set permissions of these*/
658                                                 /*    folders such that other users can not access the data inside.  On Mac OS X 10.4 and later the data inside the temporary*/
659                                                 /*    items folder is deleted at logout and at boot, but not otherwise.  Earlier version of Mac OS X would delete items inside*/
660                                                 /*    the temporary items folder after a period of inaccess.  You can ask for a temporary item in a specific domain or on a */
661                                                 /*    particular volume by FSVolumeRefNum.  If you want a location for temporary items for a short time, then use either*/
662                                                 /*    ( kUserDomain, kkTemporaryFolderType ) or ( kSystemDomain, kTemporaryFolderType ).  The kUserDomain varient will always be*/
663                                                 /*    on the same volume as the user's home folder, while the kSystemDomain version will be on the same volume as /var/tmp/ ( and*/
664                                                 /*    will probably be on the local hard drive in case the user's home is a network volume ).  If you want a location for a temporary*/
665                                                 /*    file or folder to use for saving a document, especially if you want to use FSpExchangeFile() to implement a safe-save, then*/
666                                                 /*    ask for the temporary items folder on the same volume as the file you are safe saving.*/
667                                                 /*    However, be prepared for a failure to find a temporary folder in any domain or on any volume.  Some volumes may not have*/
668                                                 /*    a location for a temporary folder, or the permissions of the volume may be such that the Folder Manager can not return*/
669                                                 /*    a temporary folder for the volume.*/
670                                                 /*    If your application creates an item in a temporary items older you should delete that item as soon as it is not needed,*/
671                                                 /*    and certainly before your application exits, since otherwise the item is consuming disk space until the user logs out or*/
672                                                 /*    restarts.  Any items left inside a temporary items folder should be moved into a folder inside the Trash folder on the disk*/
673                                                 /*    when the user logs in, inside a folder named "Recovered items", in case there is anything useful to the end user.*/
674         kChewableItemsFolderType      = k("flnt"), /* similar to kTemporaryItemsFolderType, except items in this folder are deleted at boot or when the disk is unmounted */
675         kTemporaryItemsInCacheDataFolderType = k("vtmp"), /* A folder inside the kCachedDataFolderType for the given domain which can be used for transient data*/
676         kApplicationsFolderType       = k("apps"), /*    Applications on Mac OS X are typically put in this folder ( or a subfolder ).*/
677         kVolumeRootFolderType         = k("root"), /* root folder of a volume or domain */
678         kDomainTopLevelFolderType     = k("dtop"), /* The top-level of a Folder domain, e.g. "/System"*/
679         kDomainLibraryFolderType      = k("dlib"), /* the Library subfolder of a particular domain*/
680         kUsersFolderType              = k("usrs"), /* "Users" folder, usually contains one folder for each user. */
681         kCurrentUserFolderType        = k("cusr"), /* The folder for the currently logged on user; domain passed in is ignored. */
682         kSharedUserDataFolderType     = k("sdat"), /* A Shared folder, readable & writeable by all users */
683         kCachedDataFolderType         = k("cach"), /* Contains various cache files for different clients*/
684         kDownloadsFolderType          = k("down"), /* Refers to the ~/Downloads folder*/
685         kApplicationSupportFolderType = k("asup"), /* third-party items and folders */
686 
687 
688         kDocumentsFolderType          = k("docs"), /*    User documents are typically put in this folder ( or a subfolder ).*/
689         kPictureDocumentsFolderType   = k("pdoc"), /* Refers to the "Pictures" folder in a users home directory*/
690         kMovieDocumentsFolderType     = k("mdoc"), /* Refers to the "Movies" folder in a users home directory*/
691         kMusicDocumentsFolderType     = 0xB5646F63/*'µdoc'*/, /* Refers to the "Music" folder in a users home directory*/
692         kInternetSitesFolderType      = k("site"), /* Refers to the "Sites" folder in a users home directory*/
693         kPublicFolderType             = k("pubb"), /* Refers to the "Public" folder in a users home directory*/
694 
695         kDropBoxFolderType            = k("drop") /* Refers to the "Drop Box" folder inside the user's home directory*/
696     };
697 
698     private {
699         struct FSRef {
700           char[80] hidden;    /* private to File Manager*/
701         };
702 
703         alias int Boolean;
704         alias int OSType;
705         alias short OSErr;
706         alias int OSStatus;
707         
708         extern(C) @nogc @system OSErr _dummy_FSFindFolder(short, OSType, Boolean, FSRef*) nothrow { return 0; }
709         extern(C) @nogc @system OSStatus _dummy_FSRefMakePath(const(FSRef)*, char*, uint) nothrow { return 0; }
710 
711         __gshared typeof(&_dummy_FSFindFolder) ptrFSFindFolder = null;
712         __gshared typeof(&_dummy_FSRefMakePath) ptrFSRefMakePath = null;
713     }
714 
715     shared static this()
716     {
717         enum carbonPath = "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/CarbonCore.framework/Versions/A/CarbonCore";
718 
719         import core.sys.posix.dlfcn;
720 
721         void* handle = dlopen(toStringz(carbonPath), RTLD_NOW | RTLD_LOCAL);
722         if (handle) {
723             ptrFSFindFolder = cast(typeof(ptrFSFindFolder))dlsym(handle, "FSFindFolder");
724             ptrFSRefMakePath = cast(typeof(ptrFSRefMakePath))dlsym(handle, "FSRefMakePath");
725         }
726         if (ptrFSFindFolder == null || ptrFSRefMakePath == null) {
727             debug collectException(stderr.writeln("Could not load carbon functions"));
728             if (handle) dlclose(handle);
729         }
730     }
731 
732     private @nogc @trusted bool isCarbonLoaded() nothrow
733     {
734         return ptrFSFindFolder != null && ptrFSRefMakePath != null;
735     }
736 
737     private enum OSErr noErr = 0;
738 
739     private string fsPath(short domain, OSType type, bool shouldCreate = false) nothrow @trusted
740     {
741         import std.stdio;   
742         FSRef fsref;
743         if (isCarbonLoaded() && ptrFSFindFolder(domain, type, shouldCreate, &fsref) == noErr) {
744 
745             char[2048] buf;
746             char* path = buf.ptr;
747             if (ptrFSRefMakePath(&fsref, path, buf.sizeof) == noErr) {
748                 try {
749                     return fromStringz(path).idup;
750                 }
751                 catch(Exception e) {
752 
753                 }
754             }
755         }
756         return null;
757     }
758     
759     private string writablePathImpl(StandardPath type, bool shouldCreate = false) nothrow @safe
760     {
761         final switch(type) {
762             case StandardPath.config:
763                 return fsPath(kUserDomain, kPreferencesFolderType, shouldCreate);
764             case StandardPath.cache:
765                 return fsPath(kUserDomain, kCachedDataFolderType, shouldCreate);
766             case StandardPath.data:
767                 return fsPath(kUserDomain, kApplicationSupportFolderType, shouldCreate);
768             case StandardPath.desktop:
769                 return fsPath(kUserDomain, kDesktopFolderType, shouldCreate);
770             case StandardPath.documents:
771                 return fsPath(kUserDomain, kDocumentsFolderType, shouldCreate);
772             case StandardPath.pictures:
773                 return fsPath(kUserDomain, kPictureDocumentsFolderType, shouldCreate);
774             case StandardPath.music:
775                 return fsPath(kUserDomain, kMusicDocumentsFolderType, shouldCreate);
776             case StandardPath.videos:
777                 return fsPath(kUserDomain, kMovieDocumentsFolderType, shouldCreate);
778             case StandardPath.downloads:
779                 return fsPath(kUserDomain, kDownloadsFolderType, shouldCreate);
780             case StandardPath.templates:
781                 return null;
782             case StandardPath.publicShare:
783                 return fsPath(kUserDomain, kPublicFolderType, shouldCreate);
784             case StandardPath.fonts:
785                 return fsPath(kUserDomain, kFontsFolderType, shouldCreate);
786             case StandardPath.applications:
787                 return fsPath(kUserDomain, kApplicationsFolderType, shouldCreate);
788             case StandardPath.startup:
789                 return null;
790         }
791     }
792     
793     private string writablePathImpl(StandardPath type, FolderFlag params = FolderFlag.none) nothrow @safe
794     {
795         const bool shouldCreate = (params & FolderFlag.create) != 0;
796         const bool shouldVerify = (params & FolderFlag.verify) != 0;
797         return writablePathImpl(type, shouldCreate).verifyIfNeeded(shouldVerify);
798     }
799     
800     string[] standardPaths(StandardPath type) nothrow @safe
801     {
802         string commonPath;
803         
804         switch(type) {
805             case StandardPath.fonts:
806                 commonPath = fsPath(kOnAppropriateDisk, kFontsFolderType);
807                 break;
808             case StandardPath.applications:
809                 commonPath = fsPath(kOnAppropriateDisk, kApplicationsFolderType);
810                 break;
811             case StandardPath.data:
812                 commonPath = fsPath(kOnAppropriateDisk, kApplicationSupportFolderType);
813                 break;
814             case StandardPath.cache:
815                 commonPath = fsPath(kOnAppropriateDisk, kCachedDataFolderType);
816                 break;
817             default:
818                 break;
819         }
820         
821         string[] paths;
822         string userPath = writablePath(type);
823         if (userPath.length)
824             paths ~= userPath;
825         if (commonPath.length)
826             paths ~= commonPath;
827         return paths;
828     }
829     
830 } else {
831     
832     static if (!isFreedesktop) {
833         static assert(false, "Unsupported platform");
834     } else {
835         public import xdgpaths;
836         
837         private import std.algorithm : startsWith;
838         private import std.string;
839         private import std.traits;
840             
841         //Concat two strings, but if the first one is empty, then null string is returned.
842         private string maybeConcat(string start, string path) nothrow @safe
843         {
844             return start.empty ? null : start ~ path;
845         }
846         
847         unittest
848         {
849             assert(maybeConcat(null, "path") == string.init);
850             assert(maybeConcat("path", "/file") == "path/file");
851         }
852         
853         private @trusted string getFromUserDirs(Range)(string xdgdir, string home, Range range) if (isInputRange!Range && isSomeString!(ElementType!Range))
854         {
855             foreach(line; range) {
856                 line = strip(line);
857                 auto index = xdgdir.length;
858                 if (line.startsWith(xdgdir) && line.length > index && line[index] == '=') {
859                     line = line[index+1..$];
860                     if (line.length > 2 && line[0] == '"' && line[$-1] == '"') 
861                     {
862                         line = line[1..$-1];
863                     
864                         if (line.startsWith("$HOME")) {
865                             return maybeConcat(home, assumeUnique(line[5..$]));
866                         }
867                         if (line.length == 0 || line[0] != '/') {
868                             continue;
869                         }
870                         return assumeUnique(line);
871                     }
872                 }
873             }
874             return null;
875         }
876         
877         
878         unittest
879         {
880             string content = 
881 `# Comment
882 
883 XDG_DOCUMENTS_DIR="$HOME/My Documents" 
884 XDG_MUSIC_DIR="/data/Music"
885 XDG_VIDEOS_DIR="data/Video"
886 `;
887             string home = "/home/user";
888             
889             assert(getFromUserDirs("XDG_DOCUMENTS_DIR", home, content.splitLines) == "/home/user/My Documents");
890             assert(getFromUserDirs("XDG_MUSIC_DIR", home, content.splitLines) == "/data/Music");
891             assert(getFromUserDirs("XDG_DOWNLOAD_DIR", home, content.splitLines).empty);
892             assert(getFromUserDirs("XDG_VIDEOS_DIR", home, content.splitLines).empty);
893         }
894         
895         private @trusted string getFromDefaultDirs(Range)(string key, string home, Range range) if (isInputRange!Range && isSomeString!(ElementType!Range))
896         {
897             foreach(line; range) {
898                 line = strip(line);
899                 auto index = key.length;
900                 if (line.startsWith(key) && line.length > index && line[index] == '=') 
901                 {
902                     line = line[index+1..$];
903                     return home ~ "/" ~ assumeUnique(line);
904                 }
905             }
906             return null;
907         }
908         
909         unittest
910         {
911             string content = 
912 `# Comment
913 
914 DOCUMENTS=MyDocuments
915 PICTURES=Images
916 `;
917             string home = "/home/user";
918             assert(getFromDefaultDirs("DOCUMENTS", home, content.splitLines) == "/home/user/MyDocuments");
919             assert(getFromDefaultDirs("PICTURES", home, content.splitLines) == "/home/user/Images");
920             assert(getFromDefaultDirs("VIDEOS", home, content.splitLines).empty);
921         }
922         
923         private string xdgUserDir(string key, string fallback = null) nothrow @trusted {
924             string fileName = maybeConcat(writablePath(StandardPath.config), "/user-dirs.dirs");
925             string home = homeDir();
926             try {
927                 auto f = File(fileName, "r");
928                 auto xdgdir = "XDG_" ~ key ~ "_DIR";
929                 auto path = getFromUserDirs(xdgdir, home, f.byLine());
930                 if (path.length) {
931                     return path;
932                 }
933             } catch(Exception e) {
934                 
935             }
936             
937             if (home.length) {
938                 try {
939                     auto f = File("/etc/xdg/user-dirs.defaults", "r");
940                     auto path = getFromDefaultDirs(key, home, f.byLine());
941                     if (path.length) {
942                         return path;
943                     }
944                 } catch (Exception e) {
945                     
946                 }
947                 if (fallback.length) {
948                     return home ~ fallback;
949                 }
950             }
951             return null;
952         }
953         
954         private string homeFontsPath() nothrow @trusted {
955             return maybeConcat(homeDir(), "/.fonts");
956         }
957         
958         private string[] fontPaths() nothrow @trusted
959         {
960             enum localShare = "/usr/local/share/fonts";
961             enum share = "/usr/share/fonts";
962             
963             string homeFonts = homeFontsPath();
964             if (homeFonts.length) {
965                 return [homeFonts, localShare, share];
966             } else {
967                 return [localShare, share];
968             }
969         }
970         
971         private string createIfNeeded(string path, bool shouldCreate) nothrow @trusted
972         {
973             if (path.length && shouldCreate) {
974                 bool pathExist;
975                 collectException(path.isDir, pathExist);
976                 if (pathExist || collectException(mkdirRecurse(path)) is null) {
977                     return path;
978                 } else {
979                     return null;
980                 }
981             } else {
982                 return path;
983             }
984         }
985         
986         private string writablePathImpl(StandardPath type, bool shouldCreate) nothrow @safe
987         {
988             final switch(type) {
989                 case StandardPath.config:
990                     return xdgConfigHome(null, shouldCreate);
991                 case StandardPath.cache:
992                     return xdgCacheHome(null, shouldCreate);
993                 case StandardPath.data:
994                     return xdgDataHome(null, shouldCreate);
995                 case StandardPath.desktop:
996                     return xdgUserDir("DESKTOP", "/Desktop").createIfNeeded(shouldCreate);
997                 case StandardPath.documents:
998                     return xdgUserDir("DOCUMENTS").createIfNeeded(shouldCreate);
999                 case StandardPath.pictures:
1000                     return xdgUserDir("PICTURES").createIfNeeded(shouldCreate);
1001                 case StandardPath.music:
1002                     return xdgUserDir("MUSIC").createIfNeeded(shouldCreate);
1003                 case StandardPath.videos:
1004                     return xdgUserDir("VIDEOS").createIfNeeded(shouldCreate);
1005                 case StandardPath.downloads:
1006                     return xdgUserDir("DOWNLOAD").createIfNeeded(shouldCreate);
1007                 case StandardPath.templates:
1008                     return xdgUserDir("TEMPLATES", "/Templates").createIfNeeded(shouldCreate);
1009                 case StandardPath.publicShare:
1010                     return xdgUserDir("PUBLICSHARE", "/Public").createIfNeeded(shouldCreate);
1011                 case StandardPath.fonts:
1012                     return homeFontsPath().createIfNeeded(shouldCreate);
1013                 case StandardPath.applications:
1014                     return xdgDataHome("applications", shouldCreate);
1015                 case StandardPath.startup:
1016                     return xdgConfigHome("autostart", shouldCreate);
1017             }
1018         }
1019         
1020         string writablePath(StandardPath type, FolderFlag params = FolderFlag.none) nothrow @safe
1021         {
1022             const bool shouldCreate = (params & FolderFlag.create) != 0;
1023             const bool shouldVerify = (params & FolderFlag.verify) != 0;
1024             return writablePathImpl(type, shouldCreate).verifyIfNeeded(shouldVerify);
1025         }
1026         
1027         string[] standardPaths(StandardPath type) nothrow @safe
1028         {
1029             string[] paths;
1030             
1031             switch(type) {
1032                 case StandardPath.data:
1033                     return xdgAllDataDirs();
1034                 case StandardPath.config:
1035                     return xdgAllConfigDirs();
1036                 case StandardPath.applications:
1037                     return xdgAllDataDirs("applications");
1038                 case StandardPath.startup:
1039                     return xdgAllConfigDirs("autostart");
1040                 case StandardPath.fonts:
1041                     return fontPaths();
1042                 default:
1043                     break;
1044             }
1045             
1046             string userPath = writablePath(type);
1047             if (userPath.length) {
1048                 return [userPath];
1049             }
1050             return null;
1051         }
1052     }
1053 }
1054 
1055 private bool isExecutable(string filePath) nothrow @trusted {
1056     try {
1057         version(Posix) {
1058             import core.sys.posix.unistd;
1059             return access(toStringz(filePath), X_OK) == 0;
1060         } else version(Windows) {
1061             //Use GetEffectiveRightsFromAclW?
1062             
1063             string extension = filePath.extension;
1064             const(string)[] exeExtensions = executableExtensions();
1065             foreach(ext; exeExtensions) {
1066                 if (sicmp(extension, ext) == 0)
1067                     return true;
1068             }
1069             return false;
1070             
1071         } else {
1072             static assert(false, "Unsupported platform");
1073         }
1074     } catch(Exception e) {
1075         return false;
1076     }
1077 }
1078 
1079 private string checkExecutable(string filePath) nothrow @trusted {
1080     try {
1081         if (filePath.isFile && filePath.isExecutable) {
1082             return buildNormalizedPath(filePath);
1083         } else {
1084             return null;
1085         }
1086     }
1087     catch(Exception e) {
1088         return null;
1089     }
1090 }
1091 
1092 /**
1093  * System paths where executable files can be found.
1094  * Returns: Range of paths as determined by $(B PATH) environment variable.
1095  * Note: this function does not cache its result
1096  */
1097 @trusted auto binPaths()
1098 {
1099     string pathVar;
1100     collectException(environment.get("PATH"), pathVar);
1101     return splitter(pathVar, pathVarSeparator).filter!(p => p.length != 0);
1102 }
1103 
1104 ///
1105 unittest
1106 {
1107     static if (isFreedesktop) {
1108         import xdgpaths;
1109         import std.algorithm : equal;
1110         
1111         auto pathGuard = EnvGuard("PATH");
1112         
1113         environment["PATH"] = ".:/usr/apps:/usr/local/apps:";
1114         assert(equal(binPaths(), [".", "/usr/apps", "/usr/local/apps"]));
1115     }
1116 }
1117 
1118 /**
1119  * Finds executable by $(B fileName) in the paths specified by $(B paths).
1120  * Returns: Absolute path to the existing executable file or an empty string if not found.
1121  * Params:
1122  *  fileName = Name of executable to search. If it's absolute path, this function only checks if the file is executable.
1123  *  paths = Range of directories where executable should be searched.
1124  * Note: On Windows when fileName extension is omitted, executable extensions will be automatically appended during search.
1125  */
1126 @trusted nothrow string findExecutable(Range)(string fileName, Range paths) if (isInputRange!Range && is(ElementType!Range : string))
1127 {   
1128     try {
1129         if (fileName.isAbsolute()) {
1130             return checkExecutable(fileName);
1131         } else if (fileName == fileName.baseName) {
1132             string toReturn;
1133             foreach(string path; paths) {
1134                 if (path.empty) {
1135                     continue;
1136                 }
1137                 
1138                 string candidate = buildPath(absolutePath(path), fileName);
1139                 
1140                 version(Windows) {
1141                     if (candidate.extension.empty) {
1142                         foreach(exeExtension; executableExtensions()) {
1143                             toReturn = checkExecutable(setExtension(candidate, exeExtension.toLower()));
1144                             if (toReturn.length) {
1145                                 return toReturn;
1146                             }
1147                         }
1148                     }
1149                 }
1150                 
1151                 toReturn = checkExecutable(candidate);
1152                 if (toReturn.length) {
1153                     return toReturn;
1154                 }
1155             }
1156         }
1157     } catch (Exception e) {
1158         
1159     }
1160     return null;
1161 }
1162 
1163 /**
1164  * ditto, but searches in system paths, determined by $(B PATH) environment variable.
1165  * See_Also: binPaths
1166  */
1167 @safe string findExecutable(string fileName) nothrow {
1168     try {
1169         return findExecutable(fileName, binPaths());
1170     } catch(Exception e) {
1171         return null;
1172     }
1173 }