diff --git a/Doc/deprecations/pending-removal-in-future.rst b/Doc/deprecations/pending-removal-in-future.rst index 301867416701ea..a54f98d6866e9f 100644 --- a/Doc/deprecations/pending-removal-in-future.rst +++ b/Doc/deprecations/pending-removal-in-future.rst @@ -78,6 +78,14 @@ although there is currently no date scheduled for their removal. * :mod:`os`: Calling :func:`os.register_at_fork` in a multi-threaded process. +* :mod:`os.path`: :func:`os.path.commonprefix` is deprecated, use + :func:`os.path.commonpath` for path prefixes. The :func:`os.path.commonprefix` + function is being deprecated due to having a misleading name and module. + The function is not safe to use for path prefixes despite being included in a + module about path manipulation, meaning it is easy to accidentally + introduce path traversal vulnerabilities into Python programs by using this + function. + * :class:`!pydoc.ErrorDuringImport`: A tuple value for *exc_info* parameter is deprecated, use an exception instance. diff --git a/Doc/library/os.path.rst b/Doc/library/os.path.rst index bfd59fc5a82049..409fcf4adb754b 100644 --- a/Doc/library/os.path.rst +++ b/Doc/library/os.path.rst @@ -120,6 +120,14 @@ the :mod:`glob` module.) .. versionchanged:: 3.6 Accepts a :term:`path-like object`. + .. deprecated:: next + Deprecated in favor of :func:`os.path.commonpath` for path prefixes. + The :func:`os.path.commonprefix` function is being deprecated due to + having a misleading name and module. The function is not safe to use for + path prefixes despite being included in a module about path manipulation, + meaning it is easy to accidentally introduce path traversal + vulnerabilities into Python programs by using this function. + .. function:: dirname(path, /) diff --git a/Lib/genericpath.py b/Lib/genericpath.py index 7588fe5e8020f9..71ae19190839ae 100644 --- a/Lib/genericpath.py +++ b/Lib/genericpath.py @@ -105,6 +105,15 @@ def getctime(filename, /): # Return the longest prefix of all list elements. def commonprefix(m, /): "Given a list of pathnames, returns the longest common leading component" + import warnings + warnings.warn('os.path.commonprefix() is deprecated. Use ' + 'os.path.commonpath() for longest path prefix.', + category=DeprecationWarning, + stacklevel=2) + return _commonprefix(m) + +def _commonprefix(m, /): + "Internal implementation of commonprefix()" if not m: return '' # Some people pass in a list of pathname parts to operate in an OS-agnostic # fashion; don't try to translate in that case as that's an abuse of the diff --git a/Lib/posixpath.py b/Lib/posixpath.py index 1ee27de3206c7f..8025b063397a03 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -542,7 +542,7 @@ def relpath(path, start=None): start_list = start_tail.split(sep) if start_tail else [] path_list = path_tail.split(sep) if path_tail else [] # Work out how much of the filepath is shared by start and path. - i = len(commonprefix([start_list, path_list])) + i = len(genericpath._commonprefix([start_list, path_list])) rel_list = [pardir] * (len(start_list)-i) + path_list[i:] if not rel_list: diff --git a/Lib/test/test_genericpath.py b/Lib/test/test_genericpath.py index dfc0817da45fa2..10d3f409d883c5 100644 --- a/Lib/test/test_genericpath.py +++ b/Lib/test/test_genericpath.py @@ -34,6 +34,10 @@ def test_no_argument(self): .format(self.pathmodule.__name__, attr)) def test_commonprefix(self): + with warnings_helper.check_warnings((".*commonpath().*", DeprecationWarning)): + self.do_test_commonprefix() + + def do_test_commonprefix(self): commonprefix = self.pathmodule.commonprefix self.assertEqual( commonprefix([]), @@ -606,8 +610,9 @@ def test_path_isdir(self): self.assertPathEqual(os.path.isdir) def test_path_commonprefix(self): - self.assertEqual(os.path.commonprefix([self.file_path, self.file_name]), - self.file_name) + with warnings_helper.check_warnings((".*commonpath().*", DeprecationWarning)): + self.assertEqual(os.path.commonprefix([self.file_path, self.file_name]), + self.file_name) def test_path_getsize(self): self.assertPathEqual(os.path.getsize) diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index 3a3c60dea1345f..a3728b58335e63 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -10,6 +10,7 @@ from ntpath import ALL_BUT_LAST, ALLOW_MISSING from test import support from test.support import os_helper +from test.support import warnings_helper from test.support.os_helper import FakePath from test import test_genericpath from tempfile import TemporaryFile @@ -298,6 +299,10 @@ def test_isabs(self): tester('ntpath.isabs("\\\\.\\C:")', 1) def test_commonprefix(self): + with warnings_helper.check_warnings((".*commonpath().*", DeprecationWarning)): + self.do_test_commonprefix() + + def do_test_commonprefix(self): tester('ntpath.commonprefix(["/home/swenson/spam", "/home/swen/spam"])', "/home/swen") tester('ntpath.commonprefix(["\\home\\swen\\spam", "\\home\\swen\\eggs"])', diff --git a/Lib/unittest/util.py b/Lib/unittest/util.py index 050eaed0b3f58f..c7e6b941978cd5 100644 --- a/Lib/unittest/util.py +++ b/Lib/unittest/util.py @@ -1,7 +1,6 @@ """Various utility functions.""" from collections import namedtuple, Counter -from os.path import commonprefix __unittest = True @@ -21,13 +20,23 @@ def _shorten(s, prefixlen, suffixlen): s = '%s[%d chars]%s' % (s[:prefixlen], skip, s[len(s) - suffixlen:]) return s +def _common_prefix(m): + if not m: + return "" + s1 = min(m) + s2 = max(m) + for i, c in enumerate(s1): + if c != s2[i]: + return s1[:i] + return s1 + def _common_shorten_repr(*args): args = tuple(map(safe_repr, args)) maxlen = max(map(len, args)) if maxlen <= _MAX_LENGTH: return args - prefix = commonprefix(args) + prefix = _common_prefix(args) prefixlen = len(prefix) common_len = _MAX_LENGTH - \ diff --git a/Misc/NEWS.d/next/Library/2026-02-02-12-09-38.gh-issue-74453.19h4Z5.rst b/Misc/NEWS.d/next/Library/2026-02-02-12-09-38.gh-issue-74453.19h4Z5.rst new file mode 100644 index 00000000000000..8629c834e5b0cd --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-02-02-12-09-38.gh-issue-74453.19h4Z5.rst @@ -0,0 +1,8 @@ +Deprecate :func:`os.path.commonprefix` in favor of +:func:`os.path.commonpath` for path segment prefixes. + +The :func:`os.path.commonprefix` function is being deprecated due to +having a misleading name and module. The function is not safe to use for +path prefixes despite being included in a module about path manipulation, +meaning it is easy to accidentally introduce path traversal +vulnerabilities into Python programs by using this function.