| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350 | /******************************************************************************* copyright: Copyright (c) 2006-2009 Lars Ivar Igesund, Thomas Kühne, Grzegorz Adam Hankiewicz, sleek license: BSD style: $(LICENSE) version: Initial release: December 2006 Updated and readded: August 2009 author: Lars Ivar Igesund, Thomas Kühne, Grzegorz Adam Hankiewicz, sleek Since: 0.99.9 *******************************************************************************/ module tango.sys.HomeFolder; import TextUtil = tango.text.Util; import Path = tango.io.Path; import tango.sys.Environment; version (Posix) { import tango.core.Exception; import tango.stdc.stdlib; import tango.stdc.posix.pwd; import tango.stdc.errno; private extern (C) size_t strlen (in char *); } /****************************************************************************** Returns the home folder set in the current environment. ******************************************************************************/ @property char[] homeFolder() { version (Windows) return Path.standard(Environment.get("USERPROFILE")); else return Path.standard(Environment.get("HOME")); } version (Posix) { /****************************************************************************** Performs tilde expansion in paths. There are two ways of using tilde expansion in a path. One involves using the tilde alone or followed by a path separator. In this case, the tilde will be expanded with the value of the environment variable <i>HOME</i>. The second way is putting a username after the tilde (i.e. <tt>~john/Mail</tt>). Here, the username will be searched for in the user database (i.e. <tt>/etc/passwd</tt> on Unix systems) and will expand to whatever path is stored there. The username is considered the string after the tilde ending at the first instance of a path separator. Note that using the <i>~user</i> syntax may give different values from just <i>~</i> if the environment variable doesn't match the value stored in the user database. When the environment variable version is used, the path won't be modified if the environment variable doesn't exist or it is empty. When the database version is used, the path won't be modified if the user doesn't exist in the database or there is not enough memory to perform the query. Returns: inputPath with the tilde expanded, or just inputPath if it could not be expanded. Throws: OutOfMemoryException if there is not enough memory to perform the database lookup for the <i>~user</i> syntax. Examples: ----- import tango.sys.HomeFolder; void processFile(char[] filename) { char[] path = expandTilde(filename); ... } ----- ----- import tango.sys.HomeFolder; const char[] RESOURCE_DIR_TEMPLATE = "~/.applicationrc"; char[] RESOURCE_DIR; // This gets expanded below. static this() { RESOURCE_DIR = expandTilde(RESOURCE_DIR_TEMPLATE); } ----- ******************************************************************************/ inout(char)[] expandTilde (inout(char)[] inputPath) { // Return early if there is no tilde in path. if (inputPath.length < 1 || inputPath[0] != '~') return inputPath; if (inputPath.length == 1 || inputPath[1] == '/') return expandFromEnvironment(inputPath); else return expandFromDatabase(inputPath); } /******************************************************************************* Replaces the tilde from path with the environment variable HOME. ******************************************************************************/ private inout(char)[] expandFromEnvironment(inout(char)[] path) in { assert(path.length >= 1); assert(path[0] == '~'); } body { // Get HOME and use that to replace the tilde. char[] home = homeFolder; if (home is null) return cast(inout)path; if (home[$-1] == '/') home = home[0..$-1]; return cast(inout)Path.join(home, path[1..$]); } /******************************************************************************* Replaces the tilde from path with the path from the user database. ******************************************************************************/ private inout(char)[] expandFromDatabase(inout(char)[] path) { assert(path.length > 2 || (path.length == 2 && path[1] != '/')); assert(path[0] == '~'); // Extract username, searching for path separator. char[] username; auto last_char = TextUtil.locate(path, '/'); if (last_char == path.length) { username = path[1..$].dup ~ '\0'; } else { username = path[1..last_char].dup ~ '\0'; } assert(last_char > 1); // Reserve C memory for the getpwnam_r() function. passwd result; int extra_memory_size = 5 * 1024; void* extra_memory; scope (exit) if(extra_memory) tango.stdc.stdlib.free(extra_memory); while (1) { extra_memory = tango.stdc.stdlib.malloc(extra_memory_size); if (extra_memory is null) throw new OutOfMemoryError("Not enough memory for user lookup in tilde expansion.", __LINE__); // Obtain info from database. passwd *verify; tango.stdc.errno.errno(0); if (getpwnam_r(username.ptr, &result, cast(char*)extra_memory, extra_memory_size, &verify) == 0) { // Failure if verify doesn't point at result. if (verify == &result) { auto pwdirlen = strlen(result.pw_dir); path = cast(inout)Path.join(result.pw_dir[0..pwdirlen].dup, path[last_char..$]); } return cast(inout)path; } if (tango.stdc.errno.errno() != ERANGE) throw new OutOfMemoryError("Not enough memory for user lookup in tilde expansion.", __LINE__); // extra_memory isn't large enough tango.stdc.stdlib.free(extra_memory); extra_memory_size *= 2; } } } version (Windows) { /****************************************************************************** Performs tilde expansion in paths. There are two ways of using tilde expansion in a path. One involves using the tilde alone or followed by a path separator. In this case, the tilde will be expanded with the value of the environment variable <i>HOME</i>. The second way is putting a username after the tilde (i.e. <tt>~john/Mail</tt>). Here, the username will be searched for in the user database (i.e. <tt>/etc/passwd</tt> on Unix systems) and will expand to whatever path is stored there. The username is considered the string after the tilde ending at the first instance of a path separator. Note that using the <i>~user</i> syntax may give different values from just <i>~</i> if the environment variable doesn't match the value stored in the user database. When the environment variable version is used, the path won't be modified if the environment variable doesn't exist or it is empty. When the database version is used, the path won't be modified if the user doesn't exist in the database or there is not enough memory to perform the query. Returns: inputPath with the tilde expanded, or just inputPath if it could not be expanded. Throws: OutOfMemoryException if there is not enough memory to perform the database lookup for the <i>~user</i> syntax. Examples: ----- import tango.sys.HomeFolder; void processFile(char[] filename) { char[] path = expandTilde(filename); ... } ----- ----- import tango.sys.HomeFolder; const char[] RESOURCE_DIR_TEMPLATE = "~/.applicationrc"; char[] RESOURCE_DIR; // This gets expanded below. static this() { RESOURCE_DIR = expandTilde(RESOURCE_DIR_TEMPLATE); } ----- ******************************************************************************/ inout(char)[] expandTilde(inout(char)[] inputPath) { inputPath = cast(inout(char)[])Path.standard(inputPath.dup); if (inputPath.length < 1 || inputPath[0] != '~') { return cast(inout)inputPath; } if (inputPath.length == 1 || inputPath[1] == '/') { return expandCurrentUser(inputPath); } return expandOtherUser(inputPath); } private inout(char)[] expandCurrentUser(inout(char)[] path) { auto userProfileDir = homeFolder; auto offset = TextUtil.locate(path, '/'); if (offset == path.length) { return cast(inout(char)[])userProfileDir; } return cast(inout)Path.join(userProfileDir, path[offset+1..$]); } private inout(char)[] expandOtherUser(inout(char)[] path) { auto profileDir = Path.parse(homeFolder).parent; return cast(inout)Path.join(profileDir, path[1..$]); } } /******************************************************************************* *******************************************************************************/ debug(UnitTest) { unittest { version (Posix) { // Retrieve the current home variable. char[] home = Environment.get("HOME"); // Testing when there is no environment variable. Environment.set("HOME", null); assert(expandTilde("~/") == "~/"); assert(expandTilde("~") == "~"); // Testing when an environment variable is set. Environment.set("HOME", "tango/test"); assert (Environment.get("HOME") == "tango/test"); assert(expandTilde("~/") == "tango/test/"); assert(expandTilde("~") == "tango/test"); // The same, but with a variable ending in a slash. Environment.set("HOME", "tango/test/"); assert(expandTilde("~/") == "tango/test/"); assert(expandTilde("~") == "tango/test"); // Recover original HOME variable before continuing. if (home) Environment.set("HOME", home); else Environment.set("HOME", null); // Test user expansion for root. Are there unices without /root? assert(expandTilde("~root") == "/root" || expandTilde("~root") == "/var/root"); assert(expandTilde("~root/") == "/root/" || expandTilde("~root") == "/var/root"); assert(expandTilde("~Idontexist/hey") == "~Idontexist/hey"); } } } |