Skip to content

Commit 64e6aac

Browse files
committed
Explain why os.path.commonprefix() is being deprecated
1 parent 166c39d commit 64e6aac

File tree

5 files changed

+96
-82
lines changed

5 files changed

+96
-82
lines changed

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,12 @@ although there is currently no date scheduled for their removal.
7979
* :mod:`os`: Calling :func:`os.register_at_fork` in a multi-threaded process.
8080

8181
* :mod:`os.path`: :func:`os.path.commonprefix` is deprecated, use
82-
:func:`os.path.commonpath` for path prefixes.
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.
8388

8489
* :class:`!pydoc.ErrorDuringImport`: A tuple value for *exc_info* parameter is
8590
deprecated, use an exception instance.

Doc/library/os.path.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ the :mod:`glob` module.)
122122

123123
.. deprecated:: next
124124
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.
125130

126131

127132
.. function:: dirname(path, /)

Lib/test/test_genericpath.py

Lines changed: 68 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -35,71 +35,74 @@ def test_no_argument(self):
3535

3636
def test_commonprefix(self):
3737
with warnings_helper.check_warnings((".*commonpath().*", DeprecationWarning)):
38-
commonprefix = self.pathmodule.commonprefix
39-
self.assertEqual(
40-
commonprefix([]),
41-
""
42-
)
43-
self.assertEqual(
44-
commonprefix(["/home/swenson/spam", "/home/swen/spam"]),
45-
"/home/swen"
46-
)
47-
self.assertEqual(
48-
commonprefix(["/home/swen/spam", "/home/swen/eggs"]),
49-
"/home/swen/"
50-
)
51-
self.assertEqual(
52-
commonprefix(["/home/swen/spam", "/home/swen/spam"]),
53-
"/home/swen/spam"
54-
)
55-
self.assertEqual(
56-
commonprefix(["home:swenson:spam", "home:swen:spam"]),
57-
"home:swen"
58-
)
59-
self.assertEqual(
60-
commonprefix([":home:swen:spam", ":home:swen:eggs"]),
61-
":home:swen:"
62-
)
63-
self.assertEqual(
64-
commonprefix([":home:swen:spam", ":home:swen:spam"]),
65-
":home:swen:spam"
66-
)
67-
68-
self.assertEqual(
69-
commonprefix([b"/home/swenson/spam", b"/home/swen/spam"]),
70-
b"/home/swen"
71-
)
72-
self.assertEqual(
73-
commonprefix([b"/home/swen/spam", b"/home/swen/eggs"]),
74-
b"/home/swen/"
75-
)
76-
self.assertEqual(
77-
commonprefix([b"/home/swen/spam", b"/home/swen/spam"]),
78-
b"/home/swen/spam"
79-
)
80-
self.assertEqual(
81-
commonprefix([b"home:swenson:spam", b"home:swen:spam"]),
82-
b"home:swen"
83-
)
84-
self.assertEqual(
85-
commonprefix([b":home:swen:spam", b":home:swen:eggs"]),
86-
b":home:swen:"
87-
)
88-
self.assertEqual(
89-
commonprefix([b":home:swen:spam", b":home:swen:spam"]),
90-
b":home:swen:spam"
91-
)
92-
93-
testlist = ['', 'abc', 'Xbcd', 'Xb', 'XY', 'abcd',
94-
'aXc', 'abd', 'ab', 'aX', 'abcX']
95-
for s1 in testlist:
96-
for s2 in testlist:
97-
p = commonprefix([s1, s2])
98-
self.assertStartsWith(s1, p)
99-
self.assertStartsWith(s2, p)
100-
if s1 != s2:
101-
n = len(p)
102-
self.assertNotEqual(s1[n:n+1], s2[n:n+1])
38+
self.do_test_commonprefix()
39+
40+
def do_test_commonprefix(self):
41+
commonprefix = self.pathmodule.commonprefix
42+
self.assertEqual(
43+
commonprefix([]),
44+
""
45+
)
46+
self.assertEqual(
47+
commonprefix(["/home/swenson/spam", "/home/swen/spam"]),
48+
"/home/swen"
49+
)
50+
self.assertEqual(
51+
commonprefix(["/home/swen/spam", "/home/swen/eggs"]),
52+
"/home/swen/"
53+
)
54+
self.assertEqual(
55+
commonprefix(["/home/swen/spam", "/home/swen/spam"]),
56+
"/home/swen/spam"
57+
)
58+
self.assertEqual(
59+
commonprefix(["home:swenson:spam", "home:swen:spam"]),
60+
"home:swen"
61+
)
62+
self.assertEqual(
63+
commonprefix([":home:swen:spam", ":home:swen:eggs"]),
64+
":home:swen:"
65+
)
66+
self.assertEqual(
67+
commonprefix([":home:swen:spam", ":home:swen:spam"]),
68+
":home:swen:spam"
69+
)
70+
71+
self.assertEqual(
72+
commonprefix([b"/home/swenson/spam", b"/home/swen/spam"]),
73+
b"/home/swen"
74+
)
75+
self.assertEqual(
76+
commonprefix([b"/home/swen/spam", b"/home/swen/eggs"]),
77+
b"/home/swen/"
78+
)
79+
self.assertEqual(
80+
commonprefix([b"/home/swen/spam", b"/home/swen/spam"]),
81+
b"/home/swen/spam"
82+
)
83+
self.assertEqual(
84+
commonprefix([b"home:swenson:spam", b"home:swen:spam"]),
85+
b"home:swen"
86+
)
87+
self.assertEqual(
88+
commonprefix([b":home:swen:spam", b":home:swen:eggs"]),
89+
b":home:swen:"
90+
)
91+
self.assertEqual(
92+
commonprefix([b":home:swen:spam", b":home:swen:spam"]),
93+
b":home:swen:spam"
94+
)
95+
96+
testlist = ['', 'abc', 'Xbcd', 'Xb', 'XY', 'abcd',
97+
'aXc', 'abd', 'ab', 'aX', 'abcX']
98+
for s1 in testlist:
99+
for s2 in testlist:
100+
p = commonprefix([s1, s2])
101+
self.assertStartsWith(s1, p)
102+
self.assertStartsWith(s2, p)
103+
if s1 != s2:
104+
n = len(p)
105+
self.assertNotEqual(s1[n:n+1], s2[n:n+1])
103106

104107
def test_getsize(self):
105108
filename = os_helper.TESTFN

Lib/test/test_ntpath.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -300,12 +300,15 @@ def test_isabs(self):
300300

301301
def test_commonprefix(self):
302302
with warnings_helper.check_warnings((".*commonpath().*", DeprecationWarning)):
303-
tester('ntpath.commonprefix(["/home/swenson/spam", "/home/swen/spam"])',
304-
"/home/swen")
305-
tester('ntpath.commonprefix(["\\home\\swen\\spam", "\\home\\swen\\eggs"])',
306-
"\\home\\swen\\")
307-
tester('ntpath.commonprefix(["/home/swen/spam", "/home/swen/spam"])',
308-
"/home/swen/spam")
303+
self.do_test_commonprefix()
304+
305+
def do_test_commonprefix(self):
306+
tester('ntpath.commonprefix(["/home/swenson/spam", "/home/swen/spam"])',
307+
"/home/swen")
308+
tester('ntpath.commonprefix(["\\home\\swen\\spam", "\\home\\swen\\eggs"])',
309+
"\\home\\swen\\")
310+
tester('ntpath.commonprefix(["/home/swen/spam", "/home/swen/spam"])',
311+
"/home/swen/spam")
309312

310313
def test_join(self):
311314
tester('ntpath.join("")', '')

Lib/unittest/util.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,23 @@ def _shorten(s, prefixlen, suffixlen):
2020
s = '%s[%d chars]%s' % (s[:prefixlen], skip, s[len(s) - suffixlen:])
2121
return s
2222

23-
def _commonprefix(m, /):
23+
def _common_prefix(m):
2424
if not m:
2525
return ""
26-
m = sorted(m)
27-
prefix = m[0]
28-
for item in m[1:]:
29-
for i in range(len(prefix)):
30-
if item[i] != prefix[i]:
31-
prefix = prefix[:i]
32-
break
33-
return prefix
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
3432

3533
def _common_shorten_repr(*args):
3634
args = tuple(map(safe_repr, args))
3735
maxlen = max(map(len, args))
3836
if maxlen <= _MAX_LENGTH:
3937
return args
4038

41-
prefix = _commonprefix(args)
39+
prefix = _common_prefix(args)
4240
prefixlen = len(prefix)
4341

4442
common_len = _MAX_LENGTH - \

0 commit comments

Comments
 (0)