1 /*******************************************************************************
2 
3         Copyright:
4             Copyright (c) 2006-2009 Lars Ivar Igesund, Thomas Kühne,
5               Grzegorz Adam Hankiewicz, sleek
6             Some parts copyright (c) 2009-2016 dunnhumby Germany GmbH.
7             All rights reserved.
8 
9         License:
10             Tango Dual License: 3-Clause BSD License / Academic Free License v3.0.
11             See LICENSE_TANGO.txt for details.
12 
13         Version:
14             Initial release: December 2006
15             Updated and readded: August 2009
16 
17         Authors:
18             Lars Ivar Igesund, Thomas Kühne,
19             Grzegorz Adam Hankiewicz, sleek
20 
21 *******************************************************************************/
22 
23 module ocean.sys.HomeFolder;
24 
25 import ocean.meta.types.Qualifiers;
26 import ocean.core.Verify;
27 import TextUtil = ocean.text.Util;
28 import Path = ocean.io.Path;
29 import ocean.sys.Environment;
30 version (unittest) import ocean.core.Test;
31 
32 import core.exception : onOutOfMemoryError;
33 import core.stdc.errno;
34 import core.stdc.stdlib;
35 import core.stdc.string;
36 import core.sys.posix.pwd;
37 
38 
39 /******************************************************************************
40 
41   Returns the home folder set in the current environment.
42 
43 ******************************************************************************/
44 
45 char[] homeFolder()
46 {
47    return Path.standard(Environment.get("HOME").dup);
48 }
49 
50 /*******************************************************************************
51 
52    Performs tilde expansion in paths.
53 
54    There are two ways of using tilde expansion in a path. One
55    involves using the tilde alone or followed by a path separator. In
56    this case, the tilde will be expanded with the value of the
57    environment variable <i>HOME</i>.  The second way is putting
58    a username after the tilde (i.e. <tt>~john/Mail</tt>). Here,
59    the username will be searched for in the user database
60    (i.e. <tt>/etc/passwd</tt> on Unix systems) and will expand to
61    whatever path is stored there.  The username is considered the
62    string after the tilde ending at the first instance of a path
63    separator.
64 
65    Note that using the <i>~user</i> syntax may give different
66    values from just <i>~</i> if the environment variable doesn't
67    match the value stored in the user database.
68 
69    When the environment variable version is used, the path won't
70    be modified if the environment variable doesn't exist or it
71    is empty. When the database version is used, the path won't be
72    modified if the user doesn't exist in the database or there is
73    not enough memory to perform the query.
74 
75    Returns: inputPath with the tilde expanded, or just inputPath
76    if it could not be expanded.
77 
78    Throws: OutOfMemoryException if there is not enough memory to
79       perform the database lookup for the <i>~user</i> syntax.
80 
81    Examples:
82    -----
83    import ocean.sys.HomeFolder;
84 
85    void processFile(char[] filename)
86    {
87         char[] path = expandTilde(filename);
88        ...
89    }
90    -----
91 
92    -----
93    import ocean.sys.HomeFolder;
94 
95    const char[] RESOURCE_DIR_TEMPLATE = "~/.applicationrc";
96    char[] RESOURCE_DIR;    // This gets expanded below.
97 
98    static this()
99    {
100        RESOURCE_DIR = expandTilde(RESOURCE_DIR_TEMPLATE);
101    }
102    -----
103 ******************************************************************************/
104 
105 cstring expandTilde (cstring inputPath)
106 {
107     // Return early if there is no tilde in path.
108     if (inputPath.length < 1 || inputPath[0] != '~')
109         return inputPath;
110 
111     if (inputPath.length == 1 || inputPath[1] == '/')
112         return expandFromEnvironment(inputPath);
113     else
114         return expandFromDatabase(inputPath);
115 }
116 
117 /*******************************************************************************
118 
119     Replaces the tilde from path with the environment variable HOME.
120 
121 *******************************************************************************/
122 
123 private cstring expandFromEnvironment(cstring path)
124 {
125     verify(path.length >= 1);
126     verify(path[0] == '~');
127 
128     // Get HOME and use that to replace the tilde.
129     char[] home = homeFolder;
130     if (home is null)
131         return path;
132 
133     if (home[$-1] == '/')
134         home = home[0..$-1];
135 
136     return Path.join(home, path[1..$]);
137 
138 }
139 
140 /*******************************************************************************
141 
142     Replaces the tilde from path with the path from the user database.
143 
144 *******************************************************************************/
145 
146 private cstring expandFromDatabase(cstring path)
147 {
148     verify(path.length > 2 || (path.length == 2 && path[1] != '/'));
149     verify(path[0] == '~');
150 
151     // Extract username, searching for path separator.
152     cstring username;
153     auto last_char = TextUtil.locate(path, '/');
154 
155     if (last_char == path.length)
156     {
157         username = path[1..$] ~ '\0';
158     }
159     else
160     {
161         username = path[1..last_char] ~ '\0';
162     }
163 
164     verify(last_char > 1);
165 
166     // Reserve C memory for the getpwnam_r() function.
167     passwd result;
168     int extra_memory_size = 5 * 1024;
169     void* extra_memory;
170 
171     scope (exit) if(extra_memory) core.stdc.stdlib.free(extra_memory);
172 
173     while (1)
174     {
175         extra_memory = core.stdc.stdlib.malloc(extra_memory_size);
176         if (extra_memory is null)
177             onOutOfMemoryError();
178 
179         // Obtain info from database.
180         passwd *verify;
181         core.stdc.errno.errno = 0;
182         if (getpwnam_r(username.ptr, &result, cast(char*)extra_memory, extra_memory_size,
183                        &verify) == 0)
184         {
185             // Failure if verify doesn't point at result.
186             if (verify == &result)
187             {
188                 auto pwdirlen = strlen(result.pw_dir);
189 
190                 path = Path.join(result.pw_dir[0..pwdirlen].dup, path[last_char..$]);
191             }
192 
193             return path;
194         }
195 
196         if (core.stdc.errno.errno() != ERANGE)
197             onOutOfMemoryError();
198 
199         // extra_memory isn't large enough
200         core.stdc.stdlib.free(extra_memory);
201         extra_memory_size *= 2;
202     }
203 }
204 
205 ///
206 unittest
207 {
208     // Retrieve the current home variable.
209     auto home = Environment.get("HOME");
210 
211     // Testing when there is no environment variable.
212     Environment.set("HOME", null);
213     test(expandTilde("~/") == "~/");
214     test(expandTilde("~") == "~");
215 
216     // Testing when an environment variable is set.
217     Environment.set("HOME", "ocean/test");
218     test (Environment.get("HOME") == "ocean/test");
219 
220     test(expandTilde("~/") == "ocean/test/");
221     test(expandTilde("~") == "ocean/test");
222 
223     // The same, but with a variable ending in a slash.
224     Environment.set("HOME", "ocean/test/");
225     test(expandTilde("~/") == "ocean/test/");
226     test(expandTilde("~") == "ocean/test");
227 
228     // Recover original HOME variable before continuing.
229     if (home)
230         Environment.set("HOME", home);
231     else
232         Environment.set("HOME", null);
233 
234     // Test user expansion for root. Are there unices without /root?
235     test(expandTilde("~root") == "/root" || expandTilde("~root") == "/var/root");
236     test(expandTilde("~root/") == "/root/" || expandTilde("~root") == "/var/root");
237     test(expandTilde("~Idontexist/hey") == "~Idontexist/hey");
238 }