summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVictor Stinner <vstinner@redhat.com>2018-12-05 21:56:24 +0100
committerGitHub <noreply@github.com>2018-12-05 21:56:24 +0100
commitb50b33b4ac62c8798199682e511b2028f2d3ec42 (patch)
treeda84dd6157dd685d381027c7311ebf731723cd87
parentbacc272afc165df21c607aae4ff7bfa21ae1979d (diff)
downloadcpython-git-b50b33b4ac62c8798199682e511b2028f2d3ec42.tar.gz
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)
-rw-r--r--Lib/posixpath.py9
-rw-r--r--Lib/test/test_posixpath.py68
-rw-r--r--Lib/test/test_site.py44
-rw-r--r--Misc/NEWS.d/next/Library/2018-12-05-13-37-39.bpo-10496.VH-1Lp.rst5
4 files changed, 102 insertions, 24 deletions
diff --git a/Lib/posixpath.py b/Lib/posixpath.py
index f5c2260f1e..bbc2369ce7 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 deaa577283..0663a21ff0 100644
--- a/Lib/test/test_posixpath.py
+++ b/Lib/test/test_posixpath.py
@@ -262,34 +262,56 @@ class PosixPathTest(unittest.TestCase):
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 9569135c2c..b4384ee2cf 100644
--- a/Lib/test/test_site.py
+++ b/Lib/test/test_site.py
@@ -7,6 +7,7 @@ executing have not been removed.
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 @@ class HelperFunctionsTests(unittest.TestCase):
# 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 @@ class HelperFunctionsTests(unittest.TestCase):
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 0000000000..232fcc6503
--- /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).