From 588f341e23f117b940c5a917bf77fd761c062e07 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Mon, 25 Aug 2014 19:14:52 +0100 Subject: [PATCH] Fix user-path-expansion on Windows for non-ASCII home directories. refs #1674 Signed-off-by: Daira Hopwood --- src/allmydata/test/test_util.py | 14 +++++++ src/allmydata/util/fileutil.py | 65 +++++++++++++++++++++++++++++---- 2 files changed, 71 insertions(+), 8 deletions(-) diff --git a/src/allmydata/test/test_util.py b/src/allmydata/test/test_util.py index 8eb7adb8..43038db5 100644 --- a/src/allmydata/test/test_util.py +++ b/src/allmydata/test/test_util.py @@ -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: diff --git a/src/allmydata/util/fileutil.py b/src/allmydata/util/fileutil.py index 8ed14264..613d29fe 100644 --- a/src/allmydata/util/fileutil.py +++ b/src/allmydata/util/fileutil.py @@ -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 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 + + # + GetLastError = WINFUNCTYPE(DWORD)(("GetLastError", windll.kernel32)) + # + GetEnvironmentVariableW = WINFUNCTYPE(DWORD, LPCWSTR, LPWSTR, DWORD)( + ("GetEnvironmentVariableW", windll.kernel32)) + + try: # 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)) - # - 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 , + # 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. -- 2.45.2