]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/commitdiff
Fix user-path-expansion on Windows for non-ASCII home directories. refs #1674
authorDaira Hopwood <daira@jacaranda.org>
Mon, 25 Aug 2014 18:14:52 +0000 (19:14 +0100)
committerDaira Hopwood <daira@jacaranda.org>
Sat, 11 Oct 2014 22:57:58 +0000 (23:57 +0100)
Signed-off-by: Daira Hopwood <daira@jacaranda.org>
src/allmydata/test/test_util.py
src/allmydata/util/fileutil.py

index 8eb7adb85b48a9e0b5d3937eab36d56c1db146dc..43038db5ce9c62be2b1cf7eeae3442be1e198b11 100644 (file)
@@ -521,6 +521,20 @@ class FileUtil(ReallyEqualMixin, unittest.TestCase):
         _cleanup()
         self.failIf(os.path.exists(long_filename))
 
+    def test_windows_expanduser(self):
+        def call_windows_getenv(name):
+            if name == u"HOMEDRIVE": return u"C:"
+            if name == u"HOMEPATH": return u"\\Documents and Settings\\\u0100"
+            self.fail("unexpected argument to call_windows_getenv")
+        self.patch(fileutil, 'windows_getenv', call_windows_getenv)
+
+        self.failUnlessReallyEqual(fileutil.windows_expanduser(u"~"), os.path.join(u"C:", u"\\Documents and Settings\\\u0100"))
+        self.failUnlessReallyEqual(fileutil.windows_expanduser(u"~\\foo"), os.path.join(u"C:", u"\\Documents and Settings\\\u0100", u"foo"))
+        self.failUnlessReallyEqual(fileutil.windows_expanduser(u"~/foo"), os.path.join(u"C:", u"\\Documents and Settings\\\u0100", u"foo"))
+        self.failUnlessReallyEqual(fileutil.windows_expanduser(u"a"), u"a")
+        self.failUnlessReallyEqual(fileutil.windows_expanduser(u"a~"), u"a~")
+        self.failUnlessReallyEqual(fileutil.windows_expanduser(u"a\\~\\foo"), u"a\\~\\foo")
+
     def test_disk_stats(self):
         avail = fileutil.get_available_space('.', 2**14)
         if avail == 0:
index 8ed14264ec4141807ccca7abc21b5826bc7b8a0b..613d29fef91daae17097173e7c31ab6f1e847523 100644 (file)
@@ -313,7 +313,7 @@ def abspath_expanduser_unicode(path, base=None):
     if base is not None:
         precondition_abspath(base)
 
-    path = os.path.expanduser(path)
+    path = expanduser(path)
 
     if _getfullpathname:
         # On Windows, os.path.isabs will return True for paths without a drive letter,
@@ -324,7 +324,10 @@ def abspath_expanduser_unicode(path, base=None):
             pass
 
     if not os.path.isabs(path):
-        path = os.path.join(os.getcwdu(), path)
+        if base is None:
+            path = os.path.join(os.getcwdu(), path)
+        else:
+            path = os.path.join(base, path)
 
     # We won't hit <http://bugs.python.org/issue5827> because
     # there is always at least one Unicode path component.
@@ -351,10 +354,17 @@ def to_windows_long_path(path):
 
 have_GetDiskFreeSpaceExW = False
 if sys.platform == "win32":
-    try:
-        from ctypes import WINFUNCTYPE, windll, POINTER, byref, c_ulonglong
-        from ctypes.wintypes import BOOL, DWORD, LPCWSTR
+    from ctypes import WINFUNCTYPE, windll, POINTER, byref, c_ulonglong, create_unicode_buffer
+    from ctypes.wintypes import BOOL, DWORD, LPCWSTR, LPWSTR
+
+    # <http://msdn.microsoft.com/en-us/library/ms679360%28v=VS.85%29.aspx>
+    GetLastError = WINFUNCTYPE(DWORD)(("GetLastError", windll.kernel32))
 
+    # <http://msdn.microsoft.com/en-us/library/windows/desktop/ms683188%28v=vs.85%29.aspx>
+    GetEnvironmentVariableW = WINFUNCTYPE(DWORD, LPCWSTR, LPWSTR, DWORD)(
+        ("GetEnvironmentVariableW", windll.kernel32))
+
+    try:
         # <http://msdn.microsoft.com/en-us/library/aa383742%28v=VS.85%29.aspx>
         PULARGE_INTEGER = POINTER(c_ulonglong)
 
@@ -362,14 +372,53 @@ if sys.platform == "win32":
         GetDiskFreeSpaceExW = WINFUNCTYPE(BOOL, LPCWSTR, PULARGE_INTEGER, PULARGE_INTEGER, PULARGE_INTEGER)(
             ("GetDiskFreeSpaceExW", windll.kernel32))
 
-        # <http://msdn.microsoft.com/en-us/library/ms679360%28v=VS.85%29.aspx>
-        GetLastError = WINFUNCTYPE(DWORD)(("GetLastError", windll.kernel32))
-
         have_GetDiskFreeSpaceExW = True
     except Exception:
         import traceback
         traceback.print_exc()
 
+def expanduser(path):
+    # os.path.expanduser is hopelessly broken for Unicode paths on Windows (ticket #1674).
+    if sys.platform == "win32":
+        return windows_expanduser(path)
+    else:
+        return os.path.expanduser(path)
+
+def windows_expanduser(path):
+    if not path.startswith('~'):
+        return path
+    home_drive = windows_getenv(u'HOMEDRIVE')
+    home_path = windows_getenv(u'HOMEPATH')
+    if path == '~':
+        return os.path.join(home_drive, home_path)
+    elif path.startswith('~/') or path.startswith('~\\'):
+        return os.path.join(home_drive, home_path, path[2 :])
+    else:
+        return path
+
+def windows_getenv(name):
+    # Based on <http://stackoverflow.com/questions/2608200/problems-with-umlauts-in-python-appdata-environvent-variable/2608368#2608368>,
+    # with improved error handling.
+    if not isinstance(name, unicode):
+        raise AssertionError("name must be Unicode")
+
+    n = GetEnvironmentVariableW(name, None, 0)
+    if n <= 0:
+        err = GetLastError()
+        if err == 42:
+            return None
+        raise OSError("Windows error %d attempting to read environment variable %r"
+                      % (err, name))
+
+    buf = create_unicode_buffer(u'\0'*n)
+    retval = GetEnvironmentVariableW(name, buf, n)
+    if retval <= 0:
+        err = GetLastError()
+        raise OSError("Windows error %d attempting to read environment variable %r"
+                      % (err, name))
+
+    return buf.value
+
 def get_disk_stats(whichdir, reserved_space=0):
     """Return disk statistics for the storage disk, in the form of a dict
     with the following fields.