bpo-10496: posixpath.expanduser() catchs pwd.getpwuid() error (GH-10919)
* 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.
This commit is contained in:
parent
398bd27967
commit
f2f4555d82
|
@ -246,7 +246,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:
|
||||||
|
@ -257,6 +262,8 @@ def expanduser(path):
|
||||||
try:
|
try:
|
||||||
pwent = pwd.getpwnam(name)
|
pwent = pwd.getpwnam(name)
|
||||||
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
|
||||||
if isinstance(path, bytes):
|
if isinstance(path, bytes):
|
||||||
|
|
|
@ -5,6 +5,7 @@ import warnings
|
||||||
from posixpath import realpath, abspath, dirname, basename
|
from posixpath import realpath, abspath, dirname, basename
|
||||||
from test import support, test_genericpath
|
from test import support, test_genericpath
|
||||||
from test.support import FakePath
|
from test.support import FakePath
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import posix
|
import posix
|
||||||
|
@ -242,42 +243,61 @@ class PosixPathTest(unittest.TestCase):
|
||||||
def test_expanduser(self):
|
def test_expanduser(self):
|
||||||
self.assertEqual(posixpath.expanduser("foo"), "foo")
|
self.assertEqual(posixpath.expanduser("foo"), "foo")
|
||||||
self.assertEqual(posixpath.expanduser(b"foo"), b"foo")
|
self.assertEqual(posixpath.expanduser(b"foo"), b"foo")
|
||||||
|
|
||||||
|
def test_expanduser_home_envvar(self):
|
||||||
with support.EnvironmentVarGuard() as env:
|
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 '/', '', '//', '///':
|
||||||
with self.subTest(home=home):
|
with self.subTest(home=home):
|
||||||
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
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
self.assertIsInstance(posixpath.expanduser("~/"), str)
|
|
||||||
self.assertIsInstance(posixpath.expanduser(b"~/"), bytes)
|
|
||||||
# if home directory == root directory, this test makes no sense
|
|
||||||
if posixpath.expanduser("~") != '/':
|
|
||||||
self.assertEqual(
|
|
||||||
posixpath.expanduser("~") + "/",
|
|
||||||
posixpath.expanduser("~/")
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
posixpath.expanduser(b"~") + b"/",
|
|
||||||
posixpath.expanduser(b"~/")
|
|
||||||
)
|
|
||||||
self.assertIsInstance(posixpath.expanduser("~root/"), str)
|
|
||||||
self.assertIsInstance(posixpath.expanduser("~foo/"), str)
|
|
||||||
self.assertIsInstance(posixpath.expanduser(b"~root/"), bytes)
|
|
||||||
self.assertIsInstance(posixpath.expanduser(b"~foo/"), bytes)
|
|
||||||
|
|
||||||
with support.EnvironmentVarGuard() as env:
|
def test_expanduser_pwd(self):
|
||||||
# expanduser should fall back to using the password database
|
pwd = support.import_module('pwd')
|
||||||
del env['HOME']
|
|
||||||
home = pwd.getpwuid(os.getuid()).pw_dir
|
self.assertIsInstance(posixpath.expanduser("~/"), str)
|
||||||
# $HOME can end with a trailing /, so strip it (see #17809)
|
self.assertIsInstance(posixpath.expanduser(b"~/"), bytes)
|
||||||
home = home.rstrip("/") or '/'
|
|
||||||
self.assertEqual(posixpath.expanduser("~"), home)
|
# if home directory == root directory, this test makes no sense
|
||||||
|
if posixpath.expanduser("~") != '/':
|
||||||
|
self.assertEqual(
|
||||||
|
posixpath.expanduser("~") + "/",
|
||||||
|
posixpath.expanduser("~/")
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
posixpath.expanduser(b"~") + b"/",
|
||||||
|
posixpath.expanduser(b"~/")
|
||||||
|
)
|
||||||
|
self.assertIsInstance(posixpath.expanduser("~root/"), str)
|
||||||
|
self.assertIsInstance(posixpath.expanduser("~foo/"), str)
|
||||||
|
self.assertIsInstance(posixpath.expanduser(b"~root/"), bytes)
|
||||||
|
self.assertIsInstance(posixpath.expanduser(b"~foo/"), bytes)
|
||||||
|
|
||||||
|
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.
|
||||||
|
with mock.patch.object(pwd, 'getpwuid', side_effect=KeyError), \
|
||||||
|
mock.patch.object(pwd, 'getpwnam', side_effect=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(""), ".")
|
||||||
|
|
|
@ -6,6 +6,7 @@ executing have not been removed.
|
||||||
"""
|
"""
|
||||||
import unittest
|
import unittest
|
||||||
import test.support
|
import test.support
|
||||||
|
from test import support
|
||||||
from test.support import (captured_stderr, TESTFN, EnvironmentVarGuard,
|
from test.support import (captured_stderr, TESTFN, EnvironmentVarGuard,
|
||||||
change_cwd)
|
change_cwd)
|
||||||
import builtins
|
import builtins
|
||||||
|
@ -19,6 +20,7 @@ import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sysconfig
|
import sysconfig
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from unittest import mock
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
|
||||||
# These tests are not particularly useful if Python was invoked with -S.
|
# These tests are not particularly useful if Python was invoked with -S.
|
||||||
|
@ -256,6 +258,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']
|
||||||
|
@ -274,6 +277,40 @@ 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
|
||||||
|
|
||||||
|
with EnvironmentVarGuard() as environ, \
|
||||||
|
mock.patch('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)
|
||||||
|
|
||||||
|
with mock.patch('os.path.isdir', return_value=False) as mock_isdir, \
|
||||||
|
mock.patch.object(site, 'addsitedir') as mock_addsitedir, \
|
||||||
|
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)
|
||||||
|
|
||||||
|
mock_isdir.assert_called_once_with(user_site)
|
||||||
|
mock_addsitedir.assert_not_called()
|
||||||
|
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"""
|
||||||
|
|
||||||
|
|
|
@ -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).
|
Loading…
Reference in New Issue