Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
01c152e
GH-75586 - Fix case where PATHEXT isn't applied to items in PATH (Win…
csm10495 Apr 2, 2023
0d4cd7b
PR updates
csm10495 Apr 2, 2023
5fac84a
Add tests
csm10495 Apr 2, 2023
fa145da
PR updates
csm10495 Apr 2, 2023
84a7976
line len fix
csm10495 Apr 2, 2023
63a06c4
📜🤖 Added by blurb_it.
blurb-it[bot] Apr 2, 2023
7686a23
Add changelog entry
csm10495 Apr 2, 2023
381e4fe
Double backticks
csm10495 Apr 2, 2023
e7c0b58
Update Lib/shutil.py
csm10495 Apr 2, 2023
26e3b15
PR updates
csm10495 Apr 2, 2023
6272b62
pep8 fix
csm10495 Apr 2, 2023
b6d29c8
Update Lib/test/test_shutil.py
csm10495 Apr 2, 2023
1096cb7
Update Lib/test/test_shutil.py
csm10495 Apr 2, 2023
92955d0
Update Lib/shutil.py
csm10495 Apr 3, 2023
616df6c
docs updates
csm10495 Apr 3, 2023
255e4ff
Add another test
csm10495 Apr 3, 2023
f52868d
Update Doc/library/shutil.rst
csm10495 Apr 3, 2023
6e9269f
rewording
csm10495 Apr 3, 2023
a6b7eab
Rewording
csm10495 Apr 3, 2023
7480daa
Reword whats new
csm10495 Apr 3, 2023
a48260c
Clarify
csm10495 Apr 3, 2023
6bb6f6c
Clarify how to not search cwd for exes
csm10495 Apr 3, 2023
b5f3eba
Doc updates
csm10495 Apr 3, 2023
3bf4b8d
Update Doc/library/shutil.rst
csm10495 Apr 3, 2023
be73608
Update 2023-04-02-22-04-26.gh-issue-75586.526iJm.rst
csm10495 Apr 3, 2023
0169ba9
Update Doc/library/shutil.rst
csm10495 Apr 4, 2023
499d2de
Update Lib/shutil.py
csm10495 Apr 4, 2023
3ead780
Add test for behavior
csm10495 Apr 4, 2023
f9267da
kick ci
csm10495 Apr 4, 2023
d1e68ff
Mention cwd first behavior
csm10495 Apr 4, 2023
9badf8c
Wording
csm10495 Apr 4, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
PR updates
  • Loading branch information
csm10495 committed Apr 2, 2023
commit 0d4cd7b727b82ef8cfb2ca8101fee2d91f09ef75
110 changes: 60 additions & 50 deletions Lib/shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@
elif _WINDOWS:
import nt

if sys.platform == 'win32':
try:
import ctypes
_CTYPES_SUPPORTED = True
except ImportError:
_CTYPES_SUPPORTED = False

COPY_BUFSIZE = 1024 * 1024 if _WINDOWS else 64 * 1024
# This should never be removed, see rationale in:
# https://bugs.python.org/issue43743#msg393429
Expand Down Expand Up @@ -1449,6 +1456,18 @@ def _access_check(fn, mode):
and not os.path.isdir(fn))


def _win32_need_current_directory_for_exe_path(cmd):
"""
On Windows, we can use NeedCurrentDirectoryForExePathW to figure out
if we should add the cwd to PATH when searching for executables.

If we don't have ctypes, we'll fallback to old behavior which is to always add cwd.
"""
if _CTYPES_SUPPORTED:
return bool(ctypes.windll.kernel32.NeedCurrentDirectoryForExePathW(cmd))
Comment thread
csm10495 marked this conversation as resolved.
Outdated
Comment thread
csm10495 marked this conversation as resolved.
Outdated
return True


def which(cmd, mode=os.F_OK | os.X_OK, path=None):
"""Given a command, mode, and a PATH string, return the path which
conforms to the given mode on the PATH, or None if there is no such
Expand All @@ -1459,60 +1478,55 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None):
path.

"""
# If we're given a path with a directory part, look it up directly rather
# than referring to PATH directories. This includes checking relative to the
# current directory, e.g. ./script
# If not, later logic will further verify.
if os.path.dirname(cmd):
if _access_check(cmd, mode):
return cmd

use_bytes = isinstance(cmd, bytes)

if path is None:
path = os.environ.get("PATH", None)
if path is None:
try:
path = os.confstr("CS_PATH")
except (AttributeError, ValueError):
# os.confstr() or CS_PATH is not available
path = os.defpath
# bpo-35755: Don't use os.defpath if the PATH environment variable is
# set to an empty string

# PATH='' doesn't match, whereas PATH=':' looks in the current directory
if not path:
return None

if use_bytes:
path = os.fsencode(path)
path = path.split(os.fsencode(os.pathsep))
# If we're given a path with a directory part, look it up directly rather
# than referring to PATH directories. This includes checking relative to
# the current directory, e.g. ./script
dirname, cmd = os.path.split(cmd)
if dirname:
path = [dirname]
else:
path = os.fsdecode(path)
path = path.split(os.pathsep)
if path is None:
path = os.environ.get("PATH", None)
if path is None:
try:
path = os.confstr("CS_PATH")
except (AttributeError, ValueError):
# os.confstr() or CS_PATH is not available
path = os.defpath
# bpo-35755: Don't use os.defpath if the PATH environment variable
# is set to an empty string

# PATH='' doesn't match, whereas PATH=':' looks in the current
# directory
if not path:
return None

if sys.platform == "win32":
# The current directory takes precedence on Windows.
curdir = os.curdir
if use_bytes:
curdir = os.fsencode(curdir)
if curdir not in path:
path.insert(0, curdir)
path = os.fsencode(path)
path = path.split(os.fsencode(os.pathsep))
else:
path = os.fsdecode(path)
path = path.split(os.pathsep)

if sys.platform == "win32" and _win32_need_current_directory_for_exe_path(cmd):
Comment thread
csm10495 marked this conversation as resolved.
Outdated
curdir = os.curdir
if use_bytes:
curdir = os.fsencode(curdir)
if curdir not in path:
path.insert(0, curdir)
Comment thread
csm10495 marked this conversation as resolved.
Outdated
Comment thread
csm10495 marked this conversation as resolved.
Outdated

if sys.platform == "win32":
# PATHEXT is necessary to check on Windows.
pathext_source = os.getenv("PATHEXT") or _WIN_DEFAULT_PATHEXT
pathext_source = os.getenv("PATHEXT", _WIN_DEFAULT_PATHEXT)
Comment thread
csm10495 marked this conversation as resolved.
Outdated
pathext = [ext for ext in pathext_source.split(os.pathsep) if ext]

if use_bytes:
pathext = [os.fsencode(ext) for ext in pathext]
# See if the given file matches any of the expected path extensions.
# This will allow us to short circuit when given "python.exe".
# If it does match, only test that one, otherwise we have to try
# others.
if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
files = [cmd]
else:
files = [cmd + ext for ext in pathext]

# Always try checking the originally given cmd, if it doesn't match, try pathext
files = [cmd] + [cmd + ext for ext in pathext]
else:
# On other platforms you don't have things like PATHEXT to tell you
# what file suffixes are executable, so just pass on cmd as-is.
Expand All @@ -1524,11 +1538,7 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None):
if not normdir in seen:
seen.add(normdir)
for thefile in files:
# Only allow thefile to have a directory component if the directory is the current directory.
# This prevents allowing a directory component (to be part of cmd), being applied after a PATH component.
# Unless it with reference to the current directory, e.g. ./script (or full path: C:\scriptdir\script)
if not os.path.dirname(thefile) or dir == (os.fsencode(os.curdir) if use_bytes else os.curdir):
name = os.path.join(dir, thefile)
if _access_check(name, mode):
return name
name = os.path.join(dir, thefile)
if _access_check(name, mode):
return name
return None
15 changes: 15 additions & 0 deletions Lib/test/test_shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -2191,6 +2191,21 @@ def test_pathext_applied_on_files_in_path(self):

self.assertEqual(shutil.which("test_program"), str(test_path))

# See GH-75586
@unittest.skipUnless(sys.platform == "win32", 'test specific to Windows')
def test_win32_need_current_directory_for_exe_path_true_without_ctypes(self):
with unittest.mock.patch('shutil._CTYPES_SUPPORTED', False):
self.assertTrue(shutil._win32_need_current_directory_for_exe_path('anything'))

# See GH-75586
@unittest.skipUnless(sys.platform == "win32", 'test specific to Windows')
def test_win32_need_current_directory_for_exe_path_with_ctypes(self):
with unittest.mock.patch('shutil._CTYPES_SUPPORTED', True):
with unittest.mock.patch('shutil.ctypes') as ctypes_mock:
ctypes_mock.windll.kernel32.NeedCurrentDirectoryForExePathW.return_value = 0
self.assertFalse(shutil._win32_need_current_directory_for_exe_path('test.exe'))
ctypes_mock.windll.kernel32.NeedCurrentDirectoryForExePathW.assert_called_once_with('test.exe')


class TestWhichBytes(TestWhich):
def setUp(self):
Expand Down