Commit b50b33b4 authored by Victor Stinner's avatar Victor Stinner Committed by GitHub

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 f2f4555d)
parent bacc272a
...@@ -259,7 +259,12 @@ def expanduser(path): ...@@ -259,7 +259,12 @@ def expanduser(path):
if i == 1: if i == 1:
if 'HOME' not in os.environ: if 'HOME' not in os.environ:
import pwd 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: else:
userhome = os.environ['HOME'] userhome = os.environ['HOME']
else: else:
...@@ -267,6 +272,8 @@ def expanduser(path): ...@@ -267,6 +272,8 @@ def expanduser(path):
try: try:
pwent = pwd.getpwnam(path[1:i]) pwent = pwd.getpwnam(path[1:i])
except KeyError: except KeyError:
# bpo-10496: if the user name from the path doesn't exist in the
# password database, return the path unchanged
return path return path
userhome = pwent.pw_dir userhome = pwent.pw_dir
userhome = userhome.rstrip('/') userhome = userhome.rstrip('/')
......
...@@ -262,34 +262,56 @@ class PosixPathTest(unittest.TestCase): ...@@ -262,34 +262,56 @@ class PosixPathTest(unittest.TestCase):
def test_expanduser(self): def test_expanduser(self):
self.assertEqual(posixpath.expanduser("foo"), "foo") 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 '/', '', '//', '///': for home in '/', '', '//', '///':
env['HOME'] = home env['HOME'] = home
self.assertEqual(posixpath.expanduser("~"), "/") self.assertEqual(posixpath.expanduser("~"), "/")
self.assertEqual(posixpath.expanduser("~/"), "/") self.assertEqual(posixpath.expanduser("~/"), "/")
self.assertEqual(posixpath.expanduser("~/foo"), "/foo") self.assertEqual(posixpath.expanduser("~/foo"), "/foo")
try:
import pwd def test_expanduser_pwd(self):
except ImportError: pwd = support.import_module('pwd')
pass
else: self.assertIsInstance(posixpath.expanduser("~/"), str)
self.assertIsInstance(posixpath.expanduser("~/"), basestring)
# if home directory == root directory, this test makes no sense # if home directory == root directory, this test makes no sense
if posixpath.expanduser("~") != '/': if posixpath.expanduser("~") != '/':
self.assertEqual( self.assertEqual(
posixpath.expanduser("~") + "/", posixpath.expanduser("~") + "/",
posixpath.expanduser("~/") posixpath.expanduser("~/")
) )
self.assertIsInstance(posixpath.expanduser("~root/"), basestring) self.assertIsInstance(posixpath.expanduser("~root/"), str)
self.assertIsInstance(posixpath.expanduser("~foo/"), basestring) self.assertIsInstance(posixpath.expanduser("~foo/"), str)
with test_support.EnvironmentVarGuard() as env: with support.EnvironmentVarGuard() as env:
# expanduser should fall back to using the password database # expanduser should fall back to using the password database
del env['HOME'] del env['HOME']
home = pwd.getpwuid(os.getuid()).pw_dir
# $HOME can end with a trailing /, so strip it (see #17809) home = pwd.getpwuid(os.getuid()).pw_dir
home = home.rstrip("/") or '/' # $HOME can end with a trailing /, so strip it (see #17809)
self.assertEqual(posixpath.expanduser("~"), home) 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): def test_normpath(self):
self.assertEqual(posixpath.normpath(""), ".") self.assertEqual(posixpath.normpath(""), ".")
......
...@@ -7,6 +7,7 @@ executing have not been removed. ...@@ -7,6 +7,7 @@ executing have not been removed.
import unittest import unittest
from test.test_support import run_unittest, TESTFN, EnvironmentVarGuard from test.test_support import run_unittest, TESTFN, EnvironmentVarGuard
from test.test_support import captured_output from test.test_support import captured_output
from test import support
import __builtin__ import __builtin__
import errno import errno
import os import os
...@@ -241,6 +242,7 @@ class HelperFunctionsTests(unittest.TestCase): ...@@ -241,6 +242,7 @@ class HelperFunctionsTests(unittest.TestCase):
# the call sets USER_BASE *and* USER_SITE # the call sets USER_BASE *and* USER_SITE
self.assertEqual(site.USER_SITE, user_site) self.assertEqual(site.USER_SITE, user_site)
self.assertTrue(user_site.startswith(site.USER_BASE), user_site) self.assertTrue(user_site.startswith(site.USER_BASE), user_site)
self.assertEqual(site.USER_BASE, site.getuserbase())
def test_getsitepackages(self): def test_getsitepackages(self):
site.PREFIXES = ['xoxo'] site.PREFIXES = ['xoxo']
...@@ -265,6 +267,48 @@ class HelperFunctionsTests(unittest.TestCase): ...@@ -265,6 +267,48 @@ class HelperFunctionsTests(unittest.TestCase):
wanted = os.path.join('xoxo', 'lib', 'site-packages') wanted = os.path.join('xoxo', 'lib', 'site-packages')
self.assertEqual(dirs[1], wanted) 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): class PthFile(object):
"""Helper class for handling testing of .pth files""" """Helper class for handling testing of .pth files"""
......
: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).
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment