Skip to content

Commit 957f9fe

Browse files
sethmlarsonpicnixzhugovk
authored
gh-74453: Deprecate os.path.commonprefix (#144436)
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
1 parent 01a1dd2 commit 957f9fe

File tree

8 files changed

+57
-5
lines changed

8 files changed

+57
-5
lines changed

Doc/deprecations/pending-removal-in-future.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,14 @@ although there is currently no date scheduled for their removal.
7878

7979
* :mod:`os`: Calling :func:`os.register_at_fork` in a multi-threaded process.
8080

81+
* :mod:`os.path`: :func:`os.path.commonprefix` is deprecated, use
82+
:func:`os.path.commonpath` for path prefixes. The :func:`os.path.commonprefix`
83+
function is being deprecated due to having a misleading name and module.
84+
The function is not safe to use for path prefixes despite being included in a
85+
module about path manipulation, meaning it is easy to accidentally
86+
introduce path traversal vulnerabilities into Python programs by using this
87+
function.
88+
8189
* :class:`!pydoc.ErrorDuringImport`: A tuple value for *exc_info* parameter is
8290
deprecated, use an exception instance.
8391

Doc/library/os.path.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,14 @@ the :mod:`glob` module.)
120120
.. versionchanged:: 3.6
121121
Accepts a :term:`path-like object`.
122122

123+
.. deprecated:: next
124+
Deprecated in favor of :func:`os.path.commonpath` for path prefixes.
125+
The :func:`os.path.commonprefix` function is being deprecated due to
126+
having a misleading name and module. The function is not safe to use for
127+
path prefixes despite being included in a module about path manipulation,
128+
meaning it is easy to accidentally introduce path traversal
129+
vulnerabilities into Python programs by using this function.
130+
123131

124132
.. function:: dirname(path, /)
125133

Lib/genericpath.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,15 @@ def getctime(filename, /):
105105
# Return the longest prefix of all list elements.
106106
def commonprefix(m, /):
107107
"Given a list of pathnames, returns the longest common leading component"
108+
import warnings
109+
warnings.warn('os.path.commonprefix() is deprecated. Use '
110+
'os.path.commonpath() for longest path prefix.',
111+
category=DeprecationWarning,
112+
stacklevel=2)
113+
return _commonprefix(m)
114+
115+
def _commonprefix(m, /):
116+
"Internal implementation of commonprefix()"
108117
if not m: return ''
109118
# Some people pass in a list of pathname parts to operate in an OS-agnostic
110119
# fashion; don't try to translate in that case as that's an abuse of the

Lib/posixpath.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ def relpath(path, start=None):
542542
start_list = start_tail.split(sep) if start_tail else []
543543
path_list = path_tail.split(sep) if path_tail else []
544544
# Work out how much of the filepath is shared by start and path.
545-
i = len(commonprefix([start_list, path_list]))
545+
i = len(genericpath._commonprefix([start_list, path_list]))
546546

547547
rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
548548
if not rel_list:

Lib/test/test_genericpath.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ def test_no_argument(self):
3434
.format(self.pathmodule.__name__, attr))
3535

3636
def test_commonprefix(self):
37+
with warnings_helper.check_warnings((".*commonpath().*", DeprecationWarning)):
38+
self.do_test_commonprefix()
39+
40+
def do_test_commonprefix(self):
3741
commonprefix = self.pathmodule.commonprefix
3842
self.assertEqual(
3943
commonprefix([]),
@@ -606,8 +610,9 @@ def test_path_isdir(self):
606610
self.assertPathEqual(os.path.isdir)
607611

608612
def test_path_commonprefix(self):
609-
self.assertEqual(os.path.commonprefix([self.file_path, self.file_name]),
610-
self.file_name)
613+
with warnings_helper.check_warnings((".*commonpath().*", DeprecationWarning)):
614+
self.assertEqual(os.path.commonprefix([self.file_path, self.file_name]),
615+
self.file_name)
611616

612617
def test_path_getsize(self):
613618
self.assertPathEqual(os.path.getsize)

Lib/test/test_ntpath.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from ntpath import ALL_BUT_LAST, ALLOW_MISSING
1111
from test import support
1212
from test.support import os_helper
13+
from test.support import warnings_helper
1314
from test.support.os_helper import FakePath
1415
from test import test_genericpath
1516
from tempfile import TemporaryFile
@@ -298,6 +299,10 @@ def test_isabs(self):
298299
tester('ntpath.isabs("\\\\.\\C:")', 1)
299300

300301
def test_commonprefix(self):
302+
with warnings_helper.check_warnings((".*commonpath().*", DeprecationWarning)):
303+
self.do_test_commonprefix()
304+
305+
def do_test_commonprefix(self):
301306
tester('ntpath.commonprefix(["/home/swenson/spam", "/home/swen/spam"])',
302307
"/home/swen")
303308
tester('ntpath.commonprefix(["\\home\\swen\\spam", "\\home\\swen\\eggs"])',

Lib/unittest/util.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
"""Various utility functions."""
22

33
from collections import namedtuple, Counter
4-
from os.path import commonprefix
54

65
__unittest = True
76

@@ -21,13 +20,23 @@ def _shorten(s, prefixlen, suffixlen):
2120
s = '%s[%d chars]%s' % (s[:prefixlen], skip, s[len(s) - suffixlen:])
2221
return s
2322

23+
def _common_prefix(m):
24+
if not m:
25+
return ""
26+
s1 = min(m)
27+
s2 = max(m)
28+
for i, c in enumerate(s1):
29+
if c != s2[i]:
30+
return s1[:i]
31+
return s1
32+
2433
def _common_shorten_repr(*args):
2534
args = tuple(map(safe_repr, args))
2635
maxlen = max(map(len, args))
2736
if maxlen <= _MAX_LENGTH:
2837
return args
2938

30-
prefix = commonprefix(args)
39+
prefix = _common_prefix(args)
3140
prefixlen = len(prefix)
3241

3342
common_len = _MAX_LENGTH - \
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Deprecate :func:`os.path.commonprefix` in favor of
2+
:func:`os.path.commonpath` for path segment prefixes.
3+
4+
The :func:`os.path.commonprefix` function is being deprecated due to
5+
having a misleading name and module. The function is not safe to use for
6+
path prefixes despite being included in a module about path manipulation,
7+
meaning it is easy to accidentally introduce path traversal
8+
vulnerabilities into Python programs by using this function.

0 commit comments

Comments
 (0)