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.
This commit is contained in:
parent
df5dc1c7a5
commit
3f3d82b848
@ -175,8 +175,8 @@ the :mod:`glob` module.)
|
|||||||
|
|
||||||
On Windows, :envvar:`USERPROFILE` will be used if set, otherwise a combination
|
On Windows, :envvar:`USERPROFILE` will be used if set, otherwise a combination
|
||||||
of :envvar:`HOMEPATH` and :envvar:`HOMEDRIVE` will be used. An initial
|
of :envvar:`HOMEPATH` and :envvar:`HOMEDRIVE` will be used. An initial
|
||||||
``~user`` is handled by stripping the last directory component from the created
|
``~user`` is handled by checking that the last directory component of the current
|
||||||
user path derived above.
|
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
|
If the expansion fails or if the path does not begin with a tilde, the path is
|
||||||
returned unchanged.
|
returned unchanged.
|
||||||
|
@ -705,7 +705,10 @@ call fails (for example because the path doesn't exist).
|
|||||||
.. classmethod:: Path.home()
|
.. classmethod:: Path.home()
|
||||||
|
|
||||||
Return a new path object representing the user's home directory (as
|
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()
|
>>> Path.home()
|
||||||
PosixPath('/home/antoine')
|
PosixPath('/home/antoine')
|
||||||
@ -773,7 +776,10 @@ call fails (for example because the path doesn't exist).
|
|||||||
.. method:: Path.expanduser()
|
.. method:: Path.expanduser()
|
||||||
|
|
||||||
Return a new path with expanded ``~`` and ``~user`` constructs,
|
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 = PosixPath('~/films/Monty Python')
|
||||||
>>> p.expanduser()
|
>>> p.expanduser()
|
||||||
|
@ -312,12 +312,24 @@ def expanduser(path):
|
|||||||
drive = ''
|
drive = ''
|
||||||
userhome = join(drive, os.environ['HOMEPATH'])
|
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):
|
if isinstance(path, bytes):
|
||||||
userhome = os.fsencode(userhome)
|
userhome = os.fsencode(userhome)
|
||||||
|
|
||||||
if i != 1: #~user
|
|
||||||
userhome = join(dirname(userhome), path[1:i])
|
|
||||||
|
|
||||||
return userhome + path[i:]
|
return userhome + path[i:]
|
||||||
|
|
||||||
|
|
||||||
|
@ -246,34 +246,6 @@ class _WindowsFlavour(_Flavour):
|
|||||||
# It's a path on a network drive => 'file://host/share/a/b'
|
# It's a path on a network drive => 'file://host/share/a/b'
|
||||||
return 'file:' + urlquote_from_bytes(path.as_posix().encode('utf-8'))
|
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):
|
class _PosixFlavour(_Flavour):
|
||||||
sep = '/'
|
sep = '/'
|
||||||
@ -364,21 +336,6 @@ class _PosixFlavour(_Flavour):
|
|||||||
bpath = bytes(path)
|
bpath = bytes(path)
|
||||||
return 'file://' + urlquote_from_bytes(bpath)
|
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()
|
_windows_flavour = _WindowsFlavour()
|
||||||
_posix_flavour = _PosixFlavour()
|
_posix_flavour = _PosixFlavour()
|
||||||
@ -463,6 +420,8 @@ class _NormalAccessor(_Accessor):
|
|||||||
|
|
||||||
getcwd = os.getcwd
|
getcwd = os.getcwd
|
||||||
|
|
||||||
|
expanduser = staticmethod(os.path.expanduser)
|
||||||
|
|
||||||
|
|
||||||
_normal_accessor = _NormalAccessor()
|
_normal_accessor = _NormalAccessor()
|
||||||
|
|
||||||
@ -1105,7 +1064,7 @@ class Path(PurePath):
|
|||||||
"""Return a new path pointing to the user's home directory (as
|
"""Return a new path pointing to the user's home directory (as
|
||||||
returned by os.path.expanduser('~')).
|
returned by os.path.expanduser('~')).
|
||||||
"""
|
"""
|
||||||
return cls(cls()._flavour.gethomedir(None))
|
return cls("~").expanduser()
|
||||||
|
|
||||||
def samefile(self, other_path):
|
def samefile(self, other_path):
|
||||||
"""Return whether other_path is the same or not as this file
|
"""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
|
if (not (self._drv or self._root) and
|
||||||
self._parts and self._parts[0][:1] == '~'):
|
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._from_parts([homedir] + self._parts[1:])
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
@ -503,34 +503,47 @@ class TestNtpath(NtpathTestCase):
|
|||||||
env.clear()
|
env.clear()
|
||||||
tester('ntpath.expanduser("~test")', '~test')
|
tester('ntpath.expanduser("~test")', '~test')
|
||||||
|
|
||||||
env['HOMEPATH'] = 'eric\\idle'
|
|
||||||
env['HOMEDRIVE'] = 'C:\\'
|
env['HOMEDRIVE'] = 'C:\\'
|
||||||
tester('ntpath.expanduser("~test")', 'C:\\eric\\test')
|
env['HOMEPATH'] = 'Users\\eric'
|
||||||
tester('ntpath.expanduser("~")', 'C:\\eric\\idle')
|
env['USERNAME'] = 'eric'
|
||||||
|
tester('ntpath.expanduser("~test")', 'C:\\Users\\test')
|
||||||
|
tester('ntpath.expanduser("~")', 'C:\\Users\\eric')
|
||||||
|
|
||||||
del env['HOMEDRIVE']
|
del env['HOMEDRIVE']
|
||||||
tester('ntpath.expanduser("~test")', 'eric\\test')
|
tester('ntpath.expanduser("~test")', 'Users\\test')
|
||||||
tester('ntpath.expanduser("~")', 'eric\\idle')
|
tester('ntpath.expanduser("~")', 'Users\\eric')
|
||||||
|
|
||||||
env.clear()
|
env.clear()
|
||||||
env['USERPROFILE'] = 'C:\\eric\\idle'
|
env['USERPROFILE'] = 'C:\\Users\\eric'
|
||||||
tester('ntpath.expanduser("~test")', 'C:\\eric\\test')
|
env['USERNAME'] = 'eric'
|
||||||
tester('ntpath.expanduser("~")', 'C:\\eric\\idle')
|
tester('ntpath.expanduser("~test")', 'C:\\Users\\test')
|
||||||
|
tester('ntpath.expanduser("~")', 'C:\\Users\\eric')
|
||||||
tester('ntpath.expanduser("~test\\foo\\bar")',
|
tester('ntpath.expanduser("~test\\foo\\bar")',
|
||||||
'C:\\eric\\test\\foo\\bar')
|
'C:\\Users\\test\\foo\\bar')
|
||||||
tester('ntpath.expanduser("~test/foo/bar")',
|
tester('ntpath.expanduser("~test/foo/bar")',
|
||||||
'C:\\eric\\test/foo/bar')
|
'C:\\Users\\test/foo/bar')
|
||||||
tester('ntpath.expanduser("~\\foo\\bar")',
|
tester('ntpath.expanduser("~\\foo\\bar")',
|
||||||
'C:\\eric\\idle\\foo\\bar')
|
'C:\\Users\\eric\\foo\\bar')
|
||||||
tester('ntpath.expanduser("~/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
|
# bpo-36264: ignore `HOME` when set on windows
|
||||||
env.clear()
|
env.clear()
|
||||||
env['HOME'] = 'F:\\'
|
env['HOME'] = 'F:\\'
|
||||||
env['USERPROFILE'] = 'C:\\eric\\idle'
|
env['USERPROFILE'] = 'C:\\Users\\eric'
|
||||||
tester('ntpath.expanduser("~test")', 'C:\\eric\\test')
|
env['USERNAME'] = 'eric'
|
||||||
tester('ntpath.expanduser("~")', 'C:\\eric\\idle')
|
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")
|
@unittest.skipUnless(nt, "abspath requires 'nt' module")
|
||||||
def test_abspath(self):
|
def test_abspath(self):
|
||||||
|
@ -2609,7 +2609,7 @@ class WindowsPathTest(_BasePathTest, unittest.TestCase):
|
|||||||
env.pop('USERNAME', None)
|
env.pop('USERNAME', None)
|
||||||
self.assertEqual(p1.expanduser(),
|
self.assertEqual(p1.expanduser(),
|
||||||
P('C:/Users/alice/My Documents'))
|
P('C:/Users/alice/My Documents'))
|
||||||
self.assertRaises(KeyError, p2.expanduser)
|
self.assertRaises(RuntimeError, p2.expanduser)
|
||||||
env['USERNAME'] = 'alice'
|
env['USERNAME'] = 'alice'
|
||||||
self.assertEqual(p2.expanduser(),
|
self.assertEqual(p2.expanduser(),
|
||||||
P('C:/Users/alice/My Documents'))
|
P('C:/Users/alice/My Documents'))
|
||||||
|
@ -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.
|
Loading…
x
Reference in New Issue
Block a user