summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBarney Gale <barney.gale@gmail.com>2021-04-07 22:50:13 (GMT)
committerGitHub <noreply@github.com>2021-04-07 22:50:13 (GMT)
commit3f3d82b84823eb28abeedf317bbe107bbe7f6492 (patch)
tree8b2176164b9bd84834d8381f1e2b102f8bb5f44d
parentdf5dc1c7a536abc2b497cb9506f8a37949838309 (diff)
downloadcpython-3f3d82b84823eb28abeedf317bbe107bbe7f6492.zip
cpython-3f3d82b84823eb28abeedf317bbe107bbe7f6492.tar.gz
cpython-3f3d82b84823eb28abeedf317bbe107bbe7f6492.tar.bz2
bpo-39899: os.path.expanduser(): don't guess other Windows users' home directories if the basename of the current user's home directory doesn't match their username. (GH-18841)
This makes `ntpath.expanduser()` match `pathlib.Path.expanduser()` in this regard, and is more in line with `posixpath.expanduser()`'s cautious approach. Also remove the near-duplicate implementation of `expanduser()` in pathlib, and by doing so fix a bug where KeyError could be raised when expanding another user's home directory.
-rw-r--r--Doc/library/os.path.rst4
-rw-r--r--Doc/library/pathlib.rst10
-rw-r--r--Lib/ntpath.py18
-rw-r--r--Lib/pathlib.py51
-rw-r--r--Lib/test/test_ntpath.py43
-rw-r--r--Lib/test/test_pathlib.py2
-rw-r--r--Misc/NEWS.d/next/Library/2020-03-09-20-36-07.bpo-39899.9adF3E.rst3
7 files changed, 63 insertions, 68 deletions
diff --git a/Doc/library/os.path.rst b/Doc/library/os.path.rst
index 251df4d..e2f335e 100644
--- a/Doc/library/os.path.rst
+++ b/Doc/library/os.path.rst
@@ -175,8 +175,8 @@ the :mod:`glob` module.)
On Windows, :envvar:`USERPROFILE` will be used if set, otherwise a combination
of :envvar:`HOMEPATH` and :envvar:`HOMEDRIVE` will be used. An initial
- ``~user`` is handled by stripping the last directory component from the created
- user path derived above.
+ ``~user`` is handled by checking that the last directory component of the current
+ user's home directory matches :envvar:`USERNAME`, and replacing it if so.
If the expansion fails or if the path does not begin with a tilde, the path is
returned unchanged.
diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst
index e269bf9..f15fed3 100644
--- a/Doc/library/pathlib.rst
+++ b/Doc/library/pathlib.rst
@@ -705,7 +705,10 @@ call fails (for example because the path doesn't exist).
.. classmethod:: Path.home()
Return a new path object representing the user's home directory (as
- returned by :func:`os.path.expanduser` with ``~`` construct)::
+ returned by :func:`os.path.expanduser` with ``~`` construct). If the home
+ directory can't be resolved, :exc:`RuntimeError` is raised.
+
+ ::
>>> Path.home()
PosixPath('/home/antoine')
@@ -773,7 +776,10 @@ call fails (for example because the path doesn't exist).
.. method:: Path.expanduser()
Return a new path with expanded ``~`` and ``~user`` constructs,
- as returned by :meth:`os.path.expanduser`::
+ as returned by :meth:`os.path.expanduser`. If a home directory can't be
+ resolved, :exc:`RuntimeError` is raised.
+
+ ::
>>> p = PosixPath('~/films/Monty Python')
>>> p.expanduser()
diff --git a/Lib/ntpath.py b/Lib/ntpath.py
index 6f77177..421db50 100644
--- a/Lib/ntpath.py
+++ b/Lib/ntpath.py
@@ -312,12 +312,24 @@ def expanduser(path):
drive = ''
userhome = join(drive, os.environ['HOMEPATH'])
+ if i != 1: #~user
+ # Try to guess user home directory. By default all users directories
+ # are located in the same place and are named by corresponding
+ # usernames. If current user home directory points to nonstandard
+ # place, this guess is likely wrong, and so we bail out.
+ current_user = os.environ.get('USERNAME')
+ if current_user != basename(userhome):
+ return path
+
+ target_user = path[1:i]
+ if isinstance(target_user, bytes):
+ target_user = os.fsdecode(target_user)
+ if target_user != current_user:
+ userhome = join(dirname(userhome), target_user)
+
if isinstance(path, bytes):
userhome = os.fsencode(userhome)
- if i != 1: #~user
- userhome = join(dirname(userhome), path[1:i])
-
return userhome + path[i:]
diff --git a/Lib/pathlib.py b/Lib/pathlib.py
index 9e682dc..19d45a3 100644
--- a/Lib/pathlib.py
+++ b/Lib/pathlib.py
@@ -246,34 +246,6 @@ class _WindowsFlavour(_Flavour):
# It's a path on a network drive => 'file://host/share/a/b'
return 'file:' + urlquote_from_bytes(path.as_posix().encode('utf-8'))
- def gethomedir(self, username):
- if 'USERPROFILE' in os.environ:
- userhome = os.environ['USERPROFILE']
- elif 'HOMEPATH' in os.environ:
- try:
- drv = os.environ['HOMEDRIVE']
- except KeyError:
- drv = ''
- userhome = drv + os.environ['HOMEPATH']
- else:
- raise RuntimeError("Can't determine home directory")
-
- if username:
- # Try to guess user home directory. By default all users
- # directories are located in the same place and are named by
- # corresponding usernames. If current user home directory points
- # to nonstandard place, this guess is likely wrong.
- if os.environ['USERNAME'] != username:
- drv, root, parts = self.parse_parts((userhome,))
- if parts[-1] != os.environ['USERNAME']:
- raise RuntimeError("Can't determine home directory "
- "for %r" % username)
- parts[-1] = username
- if drv or root:
- userhome = drv + root + self.join(parts[1:])
- else:
- userhome = self.join(parts)
- return userhome
class _PosixFlavour(_Flavour):
sep = '/'
@@ -364,21 +336,6 @@ class _PosixFlavour(_Flavour):
bpath = bytes(path)
return 'file://' + urlquote_from_bytes(bpath)
- def gethomedir(self, username):
- if not username:
- try:
- return os.environ['HOME']
- except KeyError:
- import pwd
- return pwd.getpwuid(os.getuid()).pw_dir
- else:
- import pwd
- try:
- return pwd.getpwnam(username).pw_dir
- except KeyError:
- raise RuntimeError("Can't determine home directory "
- "for %r" % username)
-
_windows_flavour = _WindowsFlavour()
_posix_flavour = _PosixFlavour()
@@ -463,6 +420,8 @@ class _NormalAccessor(_Accessor):
getcwd = os.getcwd
+ expanduser = staticmethod(os.path.expanduser)
+
_normal_accessor = _NormalAccessor()
@@ -1105,7 +1064,7 @@ class Path(PurePath):
"""Return a new path pointing to the user's home directory (as
returned by os.path.expanduser('~')).
"""
- return cls(cls()._flavour.gethomedir(None))
+ return cls("~").expanduser()
def samefile(self, other_path):
"""Return whether other_path is the same or not as this file
@@ -1517,7 +1476,9 @@ class Path(PurePath):
"""
if (not (self._drv or self._root) and
self._parts and self._parts[0][:1] == '~'):
- homedir = self._flavour.gethomedir(self._parts[0][1:])
+ homedir = self._accessor.expanduser(self._parts[0])
+ if homedir[:1] == "~":
+ raise RuntimeError("Could not determine home directory.")
return self._from_parts([homedir] + self._parts[1:])
return self
diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py
index a8f764f..f97aca5 100644
--- a/Lib/test/test_ntpath.py
+++ b/Lib/test/test_ntpath.py
@@ -503,34 +503,47 @@ class TestNtpath(NtpathTestCase):
env.clear()
tester('ntpath.expanduser("~test")', '~test')
- env['HOMEPATH'] = 'eric\\idle'
env['HOMEDRIVE'] = 'C:\\'
- tester('ntpath.expanduser("~test")', 'C:\\eric\\test')
- tester('ntpath.expanduser("~")', 'C:\\eric\\idle')
+ env['HOMEPATH'] = 'Users\\eric'
+ env['USERNAME'] = 'eric'
+ tester('ntpath.expanduser("~test")', 'C:\\Users\\test')
+ tester('ntpath.expanduser("~")', 'C:\\Users\\eric')
del env['HOMEDRIVE']
- tester('ntpath.expanduser("~test")', 'eric\\test')
- tester('ntpath.expanduser("~")', 'eric\\idle')
+ tester('ntpath.expanduser("~test")', 'Users\\test')
+ tester('ntpath.expanduser("~")', 'Users\\eric')
env.clear()
- env['USERPROFILE'] = 'C:\\eric\\idle'
- tester('ntpath.expanduser("~test")', 'C:\\eric\\test')
- tester('ntpath.expanduser("~")', 'C:\\eric\\idle')
+ env['USERPROFILE'] = 'C:\\Users\\eric'
+ env['USERNAME'] = 'eric'
+ tester('ntpath.expanduser("~test")', 'C:\\Users\\test')
+ tester('ntpath.expanduser("~")', 'C:\\Users\\eric')
tester('ntpath.expanduser("~test\\foo\\bar")',
- 'C:\\eric\\test\\foo\\bar')
+ 'C:\\Users\\test\\foo\\bar')
tester('ntpath.expanduser("~test/foo/bar")',
- 'C:\\eric\\test/foo/bar')
+ 'C:\\Users\\test/foo/bar')
tester('ntpath.expanduser("~\\foo\\bar")',
- 'C:\\eric\\idle\\foo\\bar')
+ 'C:\\Users\\eric\\foo\\bar')
tester('ntpath.expanduser("~/foo/bar")',
- 'C:\\eric\\idle/foo/bar')
+ 'C:\\Users\\eric/foo/bar')
# bpo-36264: ignore `HOME` when set on windows
env.clear()
env['HOME'] = 'F:\\'
- env['USERPROFILE'] = 'C:\\eric\\idle'
- tester('ntpath.expanduser("~test")', 'C:\\eric\\test')
- tester('ntpath.expanduser("~")', 'C:\\eric\\idle')
+ env['USERPROFILE'] = 'C:\\Users\\eric'
+ env['USERNAME'] = 'eric'
+ tester('ntpath.expanduser("~test")', 'C:\\Users\\test')
+ tester('ntpath.expanduser("~")', 'C:\\Users\\eric')
+
+ # bpo-39899: don't guess another user's home directory if
+ # `%USERNAME% != basename(%USERPROFILE%)`
+ env.clear()
+ env['USERPROFILE'] = 'C:\\Users\\eric'
+ env['USERNAME'] = 'idle'
+ tester('ntpath.expanduser("~test")', '~test')
+ tester('ntpath.expanduser("~")', 'C:\\Users\\eric')
+
+
@unittest.skipUnless(nt, "abspath requires 'nt' module")
def test_abspath(self):
diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py
index 2643119..0c89b6e 100644
--- a/Lib/test/test_pathlib.py
+++ b/Lib/test/test_pathlib.py
@@ -2609,7 +2609,7 @@ class WindowsPathTest(_BasePathTest, unittest.TestCase):
env.pop('USERNAME', None)
self.assertEqual(p1.expanduser(),
P('C:/Users/alice/My Documents'))
- self.assertRaises(KeyError, p2.expanduser)
+ self.assertRaises(RuntimeError, p2.expanduser)
env['USERNAME'] = 'alice'
self.assertEqual(p2.expanduser(),
P('C:/Users/alice/My Documents'))
diff --git a/Misc/NEWS.d/next/Library/2020-03-09-20-36-07.bpo-39899.9adF3E.rst b/Misc/NEWS.d/next/Library/2020-03-09-20-36-07.bpo-39899.9adF3E.rst
new file mode 100644
index 0000000..5239553
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2020-03-09-20-36-07.bpo-39899.9adF3E.rst
@@ -0,0 +1,3 @@
+:func:`os.path.expanduser()` now refuses to guess Windows home directories if the basename of current user's home directory does not match their username.
+
+:meth:`pathlib.Path.expanduser()` and :meth:`~pathlib.Path.home()` now consistently raise :exc:`RuntimeError` exception when a home directory cannot be resolved. Previously a :exc:`KeyError` exception could be raised on Windows when the ``"USERNAME"`` environment variable was unset.