[Python-checkins] bpo-10496: posixpath.expanduser() catchs pwd.getpwuid() error (GH-10919) (GH-10930)

Victor Stinner webhook-mailer at python.org
Wed Dec 5 15:56:29 EST 2018


https://github.com/python/cpython/commit/b50b33b4ac62c8798199682e511b2028f2d3ec42
commit: b50b33b4ac62c8798199682e511b2028f2d3ec42
branch: 2.7
author: Victor Stinner <vstinner at redhat.com>
committer: GitHub <noreply at github.com>
date: 2018-12-05T21:56:24+01:00
summary:

bpo-10496: posixpath.expanduser() catchs pwd.getpwuid() error (GH-10919) (GH-10930)

* posixpath.expanduser() now returns the input path unchanged if
  the HOME environment variable is not set and pwd.getpwuid() raises
  KeyError (the current user identifier doesn't exist in the password
  database).
* Add test_no_home_directory() to test_site.

(cherry picked from commit f2f4555d8287ad217a1dba7bbd93103ad4daf3a8)

files:
A Misc/NEWS.d/next/Library/2018-12-05-13-37-39.bpo-10496.VH-1Lp.rst
M Lib/posixpath.py
M Lib/test/test_posixpath.py
M Lib/test/test_site.py

diff --git a/Lib/posixpath.py b/Lib/posixpath.py
index f5c2260f1e5f..bbc2369ce7be 100644
--- a/Lib/posixpath.py
+++ b/Lib/posixpath.py
@@ -259,7 +259,12 @@ def expanduser(path):
     if i == 1:
         if 'HOME' not in os.environ:
             import pwd
-            userhome = pwd.getpwuid(os.getuid()).pw_dir
+            try:
+                userhome = pwd.getpwuid(os.getuid()).pw_dir
+            except KeyError:
+                # bpo-10496: if the current user identifier doesn't exist in the
+                # password database, return the path unchanged
+                return path
         else:
             userhome = os.environ['HOME']
     else:
@@ -267,6 +272,8 @@ def expanduser(path):
         try:
             pwent = pwd.getpwnam(path[1:i])
         except KeyError:
+            # bpo-10496: if the user name from the path doesn't exist in the
+            # password database, return the path unchanged
             return path
         userhome = pwent.pw_dir
     userhome = userhome.rstrip('/')
diff --git a/Lib/test/test_posixpath.py b/Lib/test/test_posixpath.py
index deaa5772835b..0663a21ff043 100644
--- a/Lib/test/test_posixpath.py
+++ b/Lib/test/test_posixpath.py
@@ -262,34 +262,56 @@ def fake_lstat(path):
 
     def test_expanduser(self):
         self.assertEqual(posixpath.expanduser("foo"), "foo")
-        with test_support.EnvironmentVarGuard() as env:
+
+    def test_expanduser_home_envvar(self):
+        with support.EnvironmentVarGuard() as env:
+            env['HOME'] = '/home/victor'
+            self.assertEqual(posixpath.expanduser("~"), "/home/victor")
+
+            # expanduser() strips trailing slash
+            env['HOME'] = '/home/victor/'
+            self.assertEqual(posixpath.expanduser("~"), "/home/victor")
+
             for home in '/', '', '//', '///':
                 env['HOME'] = home
                 self.assertEqual(posixpath.expanduser("~"), "/")
                 self.assertEqual(posixpath.expanduser("~/"), "/")
                 self.assertEqual(posixpath.expanduser("~/foo"), "/foo")
-        try:
-            import pwd
-        except ImportError:
-            pass
-        else:
-            self.assertIsInstance(posixpath.expanduser("~/"), basestring)
-            # if home directory == root directory, this test makes no sense
-            if posixpath.expanduser("~") != '/':
-                self.assertEqual(
-                    posixpath.expanduser("~") + "/",
-                    posixpath.expanduser("~/")
-                )
-            self.assertIsInstance(posixpath.expanduser("~root/"), basestring)
-            self.assertIsInstance(posixpath.expanduser("~foo/"), basestring)
-
-            with test_support.EnvironmentVarGuard() as env:
-                # expanduser should fall back to using the password database
-                del env['HOME']
-                home = pwd.getpwuid(os.getuid()).pw_dir
-                # $HOME can end with a trailing /, so strip it (see #17809)
-                home = home.rstrip("/") or '/'
-                self.assertEqual(posixpath.expanduser("~"), home)
+
+    def test_expanduser_pwd(self):
+        pwd = support.import_module('pwd')
+
+        self.assertIsInstance(posixpath.expanduser("~/"), str)
+
+        # if home directory == root directory, this test makes no sense
+        if posixpath.expanduser("~") != '/':
+            self.assertEqual(
+                posixpath.expanduser("~") + "/",
+                posixpath.expanduser("~/")
+            )
+        self.assertIsInstance(posixpath.expanduser("~root/"), str)
+        self.assertIsInstance(posixpath.expanduser("~foo/"), str)
+
+        with support.EnvironmentVarGuard() as env:
+            # expanduser should fall back to using the password database
+            del env['HOME']
+
+            home = pwd.getpwuid(os.getuid()).pw_dir
+            # $HOME can end with a trailing /, so strip it (see #17809)
+            home = home.rstrip("/") or '/'
+            self.assertEqual(posixpath.expanduser("~"), home)
+
+            # bpo-10496: If the HOME environment variable is not set and the
+            # user (current identifier or name in the path) doesn't exist in
+            # the password database (pwd.getuid() or pwd.getpwnam() fail),
+            # expanduser() must return the path unchanged.
+            def raise_keyerror(*args):
+                raise KeyError
+
+            with support.swap_attr(pwd, 'getpwuid', raise_keyerror), \
+                 support.swap_attr(pwd, 'getpwnam', raise_keyerror):
+                for path in ('~', '~/.local', '~vstinner/'):
+                    self.assertEqual(posixpath.expanduser(path), path)
 
     def test_normpath(self):
         self.assertEqual(posixpath.normpath(""), ".")
diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py
index 9569135c2ca2..b4384ee2cf83 100644
--- a/Lib/test/test_site.py
+++ b/Lib/test/test_site.py
@@ -7,6 +7,7 @@
 import unittest
 from test.test_support import run_unittest, TESTFN, EnvironmentVarGuard
 from test.test_support import captured_output
+from test import support
 import __builtin__
 import errno
 import os
@@ -241,6 +242,7 @@ def test_getusersitepackages(self):
         # the call sets USER_BASE *and* USER_SITE
         self.assertEqual(site.USER_SITE, user_site)
         self.assertTrue(user_site.startswith(site.USER_BASE), user_site)
+        self.assertEqual(site.USER_BASE, site.getuserbase())
 
     def test_getsitepackages(self):
         site.PREFIXES = ['xoxo']
@@ -265,6 +267,48 @@ def test_getsitepackages(self):
             wanted = os.path.join('xoxo', 'lib', 'site-packages')
             self.assertEqual(dirs[1], wanted)
 
+    def test_no_home_directory(self):
+        # bpo-10496: getuserbase() and getusersitepackages() must not fail if
+        # the current user has no home directory (if expanduser() returns the
+        # path unchanged).
+        site.USER_SITE = None
+        site.USER_BASE = None
+        sysconfig._CONFIG_VARS = None
+
+        with EnvironmentVarGuard() as environ, \
+             support.swap_attr(os.path, 'expanduser', lambda path: path):
+
+            del environ['PYTHONUSERBASE']
+            del environ['APPDATA']
+
+            user_base = site.getuserbase()
+            self.assertTrue(user_base.startswith('~' + os.sep),
+                            user_base)
+
+            user_site = site.getusersitepackages()
+            self.assertTrue(user_site.startswith(user_base), user_site)
+
+        def fake_isdir(path):
+            fake_isdir.arg = path
+            return False
+        fake_isdir.arg = None
+
+        def must_not_be_called(*args):
+            raise AssertionError
+
+        with support.swap_attr(os.path, 'isdir', fake_isdir), \
+             support.swap_attr(site, 'addsitedir', must_not_be_called), \
+             support.swap_attr(site, 'ENABLE_USER_SITE', True):
+
+            # addusersitepackages() must not add user_site to sys.path
+            # if it is not an existing directory
+            known_paths = set()
+            site.addusersitepackages(known_paths)
+
+            self.assertEqual(fake_isdir.arg, user_site)
+            self.assertFalse(known_paths)
+
+
 class PthFile(object):
     """Helper class for handling testing of .pth files"""
 
diff --git a/Misc/NEWS.d/next/Library/2018-12-05-13-37-39.bpo-10496.VH-1Lp.rst b/Misc/NEWS.d/next/Library/2018-12-05-13-37-39.bpo-10496.VH-1Lp.rst
new file mode 100644
index 000000000000..232fcc6503b0
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2018-12-05-13-37-39.bpo-10496.VH-1Lp.rst
@@ -0,0 +1,5 @@
+:func:`posixpath.expanduser` now returns the input *path* unchanged if the
+``HOME`` environment variable is not set and the current user has no home
+directory (if the current user identifier doesn't exist in the password
+database). This change fix the :mod:`site` module if the current user doesn't
+exist in the password database (if the user has no home directory).



More information about the Python-checkins mailing list