1 /*******************************************************************************
2 
3         copyright:      Copyright (c) 2006-2009 Lars Ivar Igesund, Thomas Kühne,
4                                            Grzegorz Adam Hankiewicz, sleek
5 
6         license:        BSD style: $(LICENSE)
7 
8         version:        Initial release: December 2006
9                         Updated and readded: August 2009
10 
11         author:         Lars Ivar Igesund, Thomas Kühne,
12                         Grzegorz Adam Hankiewicz, sleek
13 
14         Since:          0.99.9
15 
16 *******************************************************************************/
17 
18 module tango.sys.HomeFolder;
19 
20 import TextUtil = tango.text.Util;
21 import Path = tango.io.Path;
22 import tango.sys.Environment;
23 
24 version (Posix)
25 {
26     import tango.core.Exception;
27     import tango.stdc.stdlib;
28     import tango.stdc.posix.pwd;
29     import tango.stdc.errno;
30 
31     private extern (C) size_t strlen (in char *);
32 }
33 
34 
35 /******************************************************************************
36 
37   Returns the home folder set in the current environment.
38 
39 ******************************************************************************/
40 
41 @property char[] homeFolder()
42 {
43     version (Windows)
44         return Path.standard(Environment.get("USERPROFILE"));
45     else
46         return Path.standard(Environment.get("HOME"));
47 }
48 
49 version (Posix)
50 {
51 
52     /******************************************************************************
53 
54         Performs tilde expansion in paths.
55 
56         There are two ways of using tilde expansion in a path. One
57         involves using the tilde alone or followed by a path separator. In
58         this case, the tilde will be expanded with the value of the
59         environment variable <i>HOME</i>.  The second way is putting
60         a username after the tilde (i.e. <tt>~john/Mail</tt>). Here,
61         the username will be searched for in the user database
62         (i.e. <tt>/etc/passwd</tt> on Unix systems) and will expand to
63         whatever path is stored there.  The username is considered the
64         string after the tilde ending at the first instance of a path
65         separator.
66 
67         Note that using the <i>~user</i> syntax may give different
68         values from just <i>~</i> if the environment variable doesn't
69         match the value stored in the user database.
70 
71         When the environment variable version is used, the path won't
72         be modified if the environment variable doesn't exist or it
73         is empty. When the database version is used, the path won't be
74         modified if the user doesn't exist in the database or there is
75         not enough memory to perform the query.
76 
77         Returns: inputPath with the tilde expanded, or just inputPath
78         if it could not be expanded.
79 
80         Throws: OutOfMemoryException if there is not enough memory to
81                 perform the database lookup for the <i>~user</i> syntax.
82 
83         Examples:
84         -----
85         import tango.sys.HomeFolder;
86 
87         void processFile(char[] filename)
88         {
89              char[] path = expandTilde(filename);
90             ...
91         }
92         -----
93 
94         -----
95         import tango.sys.HomeFolder;
96 
97         const char[] RESOURCE_DIR_TEMPLATE = "~/.applicationrc";
98         char[] RESOURCE_DIR;    // This gets expanded below.
99 
100         static this()
101         {
102             RESOURCE_DIR = expandTilde(RESOURCE_DIR_TEMPLATE);
103         }
104         -----
105     ******************************************************************************/
106 
107     inout(char)[] expandTilde (inout(char)[] inputPath)
108     {
109             // Return early if there is no tilde in path.
110             if (inputPath.length < 1 || inputPath[0] != '~')
111                 return inputPath;
112 
113             if (inputPath.length == 1 || inputPath[1] == '/')
114                 return expandFromEnvironment(inputPath);
115             else
116                 return expandFromDatabase(inputPath);
117     }
118 
119     /*******************************************************************************
120 
121             Replaces the tilde from path with the environment variable
122             HOME.
123 
124     ******************************************************************************/
125 
126     private inout(char)[] expandFromEnvironment(inout(char)[] path)
127     in
128     {
129         assert(path.length >= 1);
130         assert(path[0] == '~');
131     }
132     body
133     {
134         // Get HOME and use that to replace the tilde.
135         char[] home = homeFolder;
136         if (home is null)
137             return cast(inout)path;
138 
139         if (home[$-1] == '/')
140             home = home[0..$-1];
141 
142         return cast(inout)Path.join(home, path[1..$]);
143 
144     }
145 
146     /*******************************************************************************
147 
148             Replaces the tilde from path with the path from the user
149             database.
150 
151     ******************************************************************************/
152 
153     private inout(char)[] expandFromDatabase(inout(char)[] path)
154     {
155         assert(path.length > 2 || (path.length == 2 && path[1] != '/'));
156         assert(path[0] == '~');
157 
158         // Extract username, searching for path separator.
159         char[] username;
160         auto last_char = TextUtil.locate(path, '/');
161 
162         if (last_char == path.length)
163         {
164             username = path[1..$].dup ~ '\0';
165         }
166         else
167         {
168             username = path[1..last_char].dup ~ '\0';
169         }
170 
171         assert(last_char > 1);
172 
173         // Reserve C memory for the getpwnam_r() function.
174         passwd result;
175         int extra_memory_size = 5 * 1024;
176         void* extra_memory;
177 
178         scope (exit) if(extra_memory) tango.stdc.stdlib.free(extra_memory);
179 
180         while (1)
181         {
182             extra_memory = tango.stdc.stdlib.malloc(extra_memory_size);
183             if (extra_memory is null)
184                 throw new OutOfMemoryError("Not enough memory for user lookup in tilde expansion.", __LINE__);
185 
186             // Obtain info from database.
187             passwd *verify;
188             tango.stdc.errno.errno(0);
189             if (getpwnam_r(username.ptr, &result, cast(char*)extra_memory, extra_memory_size,
190                 &verify) == 0)
191             {
192                 // Failure if verify doesn't point at result.
193                 if (verify == &result)
194                 {
195                     auto pwdirlen = strlen(result.pw_dir);
196 
197                     path = cast(inout)Path.join(result.pw_dir[0..pwdirlen].dup, path[last_char..$]);
198                 }
199 
200                 return cast(inout)path;
201             }
202 
203             if (tango.stdc.errno.errno() != ERANGE)
204                 throw new OutOfMemoryError("Not enough memory for user lookup in tilde expansion.", __LINE__);
205 
206             // extra_memory isn't large enough
207             tango.stdc.stdlib.free(extra_memory);
208             extra_memory_size *= 2;
209         }
210     }
211 
212 }
213 
214 version (Windows)
215 {
216 
217     /******************************************************************************
218 
219         Performs tilde expansion in paths.
220 
221         There are two ways of using tilde expansion in a path. One
222         involves using the tilde alone or followed by a path separator. In
223         this case, the tilde will be expanded with the value of the
224         environment variable <i>HOME</i>.  The second way is putting
225         a username after the tilde (i.e. <tt>~john/Mail</tt>). Here,
226         the username will be searched for in the user database
227         (i.e. <tt>/etc/passwd</tt> on Unix systems) and will expand to
228         whatever path is stored there.  The username is considered the
229         string after the tilde ending at the first instance of a path
230         separator.
231 
232         Note that using the <i>~user</i> syntax may give different
233         values from just <i>~</i> if the environment variable doesn't
234         match the value stored in the user database.
235 
236         When the environment variable version is used, the path won't
237         be modified if the environment variable doesn't exist or it
238         is empty. When the database version is used, the path won't be
239         modified if the user doesn't exist in the database or there is
240         not enough memory to perform the query.
241 
242         Returns: inputPath with the tilde expanded, or just inputPath
243         if it could not be expanded.
244 
245         Throws: OutOfMemoryException if there is not enough memory to
246                 perform the database lookup for the <i>~user</i> syntax.
247 
248         Examples:
249         -----
250         import tango.sys.HomeFolder;
251 
252         void processFile(char[] filename)
253         {
254              char[] path = expandTilde(filename);
255             ...
256         }
257         -----
258 
259         -----
260         import tango.sys.HomeFolder;
261 
262         const char[] RESOURCE_DIR_TEMPLATE = "~/.applicationrc";
263         char[] RESOURCE_DIR;    // This gets expanded below.
264 
265         static this()
266         {
267             RESOURCE_DIR = expandTilde(RESOURCE_DIR_TEMPLATE);
268         }
269         -----
270     ******************************************************************************/
271 
272     inout(char)[] expandTilde(inout(char)[] inputPath)
273     {
274         inputPath = cast(inout(char)[])Path.standard(inputPath.dup);
275 
276         if (inputPath.length < 1 || inputPath[0] != '~') {
277             return cast(inout)inputPath;
278         }
279 
280         if (inputPath.length == 1 || inputPath[1] == '/') {
281             return expandCurrentUser(inputPath);
282         }
283 
284         return expandOtherUser(inputPath);
285     }
286 
287     private inout(char)[] expandCurrentUser(inout(char)[] path)
288     {
289         auto userProfileDir = homeFolder;
290         auto offset = TextUtil.locate(path, '/');
291 
292         if (offset == path.length) {
293             return cast(inout(char)[])userProfileDir;
294         }
295 
296         return cast(inout)Path.join(userProfileDir, path[offset+1..$]);
297     }
298 
299     private inout(char)[] expandOtherUser(inout(char)[] path)
300     {
301         auto profileDir = Path.parse(homeFolder).parent;
302         return cast(inout)Path.join(profileDir, path[1..$]);
303     }
304 }
305 
306 /*******************************************************************************
307 
308 *******************************************************************************/
309 
310 debug(UnitTest) {
311 
312 unittest
313 {
314     version (Posix)
315     {
316     // Retrieve the current home variable.
317     char[] home = Environment.get("HOME");
318 
319     // Testing when there is no environment variable.
320     Environment.set("HOME", null);
321     assert(expandTilde("~/") == "~/");
322     assert(expandTilde("~") == "~");
323 
324     // Testing when an environment variable is set.
325     Environment.set("HOME", "tango/test");
326     assert (Environment.get("HOME") == "tango/test");
327 
328     assert(expandTilde("~/") == "tango/test/");
329     assert(expandTilde("~") == "tango/test");
330 
331     // The same, but with a variable ending in a slash.
332     Environment.set("HOME", "tango/test/");
333     assert(expandTilde("~/") == "tango/test/");
334     assert(expandTilde("~") == "tango/test");
335 
336     // Recover original HOME variable before continuing.
337     if (home)
338         Environment.set("HOME", home);
339     else
340         Environment.set("HOME", null);
341 
342     // Test user expansion for root. Are there unices without /root?
343     assert(expandTilde("~root") == "/root" || expandTilde("~root") == "/var/root");
344     assert(expandTilde("~root/") == "/root/" || expandTilde("~root") == "/var/root");
345     assert(expandTilde("~Idontexist/hey") == "~Idontexist/hey");
346     }
347 }
348 }
349