Skip to content

Commit b50b33b

Browse files
authored
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 f2f4555)
1 parent bacc272 commit b50b33b

File tree

4 files changed

+102
-24
lines changed

4 files changed

+102
-24
lines changed

‎Lib/posixpath.py‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,14 +259,21 @@ def expanduser(path):
259259
if i == 1:
260260
if 'HOME' not in os.environ:
261261
import pwd
262-
userhome = pwd.getpwuid(os.getuid()).pw_dir
262+
try:
263+
userhome = pwd.getpwuid(os.getuid()).pw_dir
264+
except KeyError:
265+
# bpo-10496: if the current user identifier doesn't exist in the
266+
# password database, return the path unchanged
267+
return path
263268
else:
264269
userhome = os.environ['HOME']
265270
else:
266271
import pwd
267272
try:
268273
pwent = pwd.getpwnam(path[1:i])
269274
except KeyError:
275+
# bpo-10496: if the user name from the path doesn't exist in the
276+
# password database, return the path unchanged
270277
return path
271278
userhome = pwent.pw_dir
272279
userhome = userhome.rstrip('/')

‎Lib/test/test_posixpath.py‎

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -262,34 +262,56 @@ def fake_lstat(path):
262262

263263
def test_expanduser(self):
264264
self.assertEqual(posixpath.expanduser("foo"), "foo")
265-
with test_support.EnvironmentVarGuard() as env:
265+
266+
def test_expanduser_home_envvar(self):
267+
with support.EnvironmentVarGuard() as env:
268+
env['HOME'] = '/home/victor'
269+
self.assertEqual(posixpath.expanduser("~"), "/home/victor")
270+
271+
# expanduser() strips trailing slash
272+
env['HOME'] = '/home/victor/'
273+
self.assertEqual(posixpath.expanduser("~"), "/home/victor")
274+
266275
for home in '/', '', '//', '///':
267276
env['HOME'] = home
268277
self.assertEqual(posixpath.expanduser("~"), "/")
269278
self.assertEqual(posixpath.expanduser("~/"), "/")
270279
self.assertEqual(posixpath.expanduser("~/foo"), "/foo")
271-
try:
272-
import pwd
273-
except ImportError:
274-
pass
275-
else:
276-
self.assertIsInstance(posixpath.expanduser("~/"), basestring)
277-
# if home directory == root directory, this test makes no sense
278-
if posixpath.expanduser("~") != '/':
279-
self.assertEqual(
280-
posixpath.expanduser("~") + "/",
281-
posixpath.expanduser("~/")
282-
)
283-
self.assertIsInstance(posixpath.expanduser("~root/"), basestring)
284-
self.assertIsInstance(posixpath.expanduser("~foo/"), basestring)
285-
286-
with test_support.EnvironmentVarGuard() as env:
287-
# expanduser should fall back to using the password database
288-
del env['HOME']
289-
home = pwd.getpwuid(os.getuid()).pw_dir
290-
# $HOME can end with a trailing /, so strip it (see #17809)
291-
home = home.rstrip("/") or '/'
292-
self.assertEqual(posixpath.expanduser("~"), home)
280+
281+
def test_expanduser_pwd(self):
282+
pwd = support.import_module('pwd')
283+
284+
self.assertIsInstance(posixpath.expanduser("~/"), str)
285+
286+
# if home directory == root directory, this test makes no sense
287+
if posixpath.expanduser("~") != '/':
288+
self.assertEqual(
289+
posixpath.expanduser("~") + "/",
290+
posixpath.expanduser("~/")
291+
)
292+
self.assertIsInstance(posixpath.expanduser("~root/"), str)
293+
self.assertIsInstance(posixpath.expanduser("~foo/"), str)
294+
295+
with support.EnvironmentVarGuard() as env:
296+
# expanduser should fall back to using the password database
297+
del env['HOME']
298+
299+
home = pwd.getpwuid(os.getuid()).pw_dir
300+
# $HOME can end with a trailing /, so strip it (see #17809)
301+
home = home.rstrip("/") or '/'
302+
self.assertEqual(posixpath.expanduser("~"), home)
303+
304+
# bpo-10496: If the HOME environment variable is not set and the
305+
# user (current identifier or name in the path) doesn't exist in
306+
# the password database (pwd.getuid() or pwd.getpwnam() fail),
307+
# expanduser() must return the path unchanged.
308+
def raise_keyerror(*args):
309+
raise KeyError
310+
311+
with support.swap_attr(pwd, 'getpwuid', raise_keyerror), \
312+
support.swap_attr(pwd, 'getpwnam', raise_keyerror):
313+
for path in ('~', '~/.local', '~vstinner/'):
314+
self.assertEqual(posixpath.expanduser(path), path)
293315

294316
def test_normpath(self):
295317
self.assertEqual(posixpath.normpath(""), ".")

‎Lib/test/test_site.py‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import unittest
88
from test.test_support import run_unittest, TESTFN, EnvironmentVarGuard
99
from test.test_support import captured_output
10+
from test import support
1011
import __builtin__
1112
import errno
1213
import os
@@ -241,6 +242,7 @@ def test_getusersitepackages(self):
241242
# the call sets USER_BASE *and* USER_SITE
242243
self.assertEqual(site.USER_SITE, user_site)
243244
self.assertTrue(user_site.startswith(site.USER_BASE), user_site)
245+
self.assertEqual(site.USER_BASE, site.getuserbase())
244246

245247
def test_getsitepackages(self):
246248
site.PREFIXES = ['xoxo']
@@ -265,6 +267,48 @@ def test_getsitepackages(self):
265267
wanted = os.path.join('xoxo', 'lib', 'site-packages')
266268
self.assertEqual(dirs[1], wanted)
267269

270+
def test_no_home_directory(self):
271+
# bpo-10496: getuserbase() and getusersitepackages() must not fail if
272+
# the current user has no home directory (if expanduser() returns the
273+
# path unchanged).
274+
site.USER_SITE = None
275+
site.USER_BASE = None
276+
sysconfig._CONFIG_VARS = None
277+
278+
with EnvironmentVarGuard() as environ, \
279+
support.swap_attr(os.path, 'expanduser', lambda path: path):
280+
281+
del environ['PYTHONUSERBASE']
282+
del environ['APPDATA']
283+
284+
user_base = site.getuserbase()
285+
self.assertTrue(user_base.startswith('~' + os.sep),
286+
user_base)
287+
288+
user_site = site.getusersitepackages()
289+
self.assertTrue(user_site.startswith(user_base), user_site)
290+
291+
def fake_isdir(path):
292+
fake_isdir.arg = path
293+
return False
294+
fake_isdir.arg = None
295+
296+
def must_not_be_called(*args):
297+
raise AssertionError
298+
299+
with support.swap_attr(os.path, 'isdir', fake_isdir), \
300+
support.swap_attr(site, 'addsitedir', must_not_be_called), \
301+
support.swap_attr(site, 'ENABLE_USER_SITE', True):
302+
303+
# addusersitepackages() must not add user_site to sys.path
304+
# if it is not an existing directory
305+
known_paths = set()
306+
site.addusersitepackages(known_paths)
307+
308+
self.assertEqual(fake_isdir.arg, user_site)
309+
self.assertFalse(known_paths)
310+
311+
268312
class PthFile(object):
269313
"""Helper class for handling testing of .pth files"""
270314

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
:func:`posixpath.expanduser` now returns the input *path* unchanged if the
2+
``HOME`` environment variable is not set and the current user has no home
3+
directory (if the current user identifier doesn't exist in the password
4+
database). This change fix the :mod:`site` module if the current user doesn't
5+
exist in the password database (if the user has no home directory).

0 commit comments

Comments
 (0)