mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 23:21:29 +00:00 
			
		
		
		
	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
					
				
					 7 changed files with 63 additions and 68 deletions
				
			
		| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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:]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -246,34 +246,6 @@ def make_uri(self, path):
 | 
			
		|||
            # 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 @@ def make_uri(self, path):
 | 
			
		|||
        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 @@ def group(self, path):
 | 
			
		|||
 | 
			
		||||
    getcwd = os.getcwd
 | 
			
		||||
 | 
			
		||||
    expanduser = staticmethod(os.path.expanduser)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
_normal_accessor = _NormalAccessor()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1105,7 +1064,7 @@ def home(cls):
 | 
			
		|||
        """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 @@ def expanduser(self):
 | 
			
		|||
        """
 | 
			
		||||
        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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -503,34 +503,47 @@ def test_expanduser(self):
 | 
			
		|||
            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):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2609,7 +2609,7 @@ def check():
 | 
			
		|||
                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'))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue