gh-136063: fix quadratic-complexity parsing in email.message._parseparam (GH-136072)
This commit is contained in:
@@ -74,19 +74,25 @@ def _parseparam(s):
|
|||||||
# RDM This might be a Header, so for now stringify it.
|
# RDM This might be a Header, so for now stringify it.
|
||||||
s = ';' + str(s)
|
s = ';' + str(s)
|
||||||
plist = []
|
plist = []
|
||||||
while s[:1] == ';':
|
start = 0
|
||||||
s = s[1:]
|
while s.find(';', start) == start:
|
||||||
end = s.find(';')
|
start += 1
|
||||||
while end > 0 and (s.count('"', 0, end) - s.count('\\"', 0, end)) % 2:
|
end = s.find(';', start)
|
||||||
end = s.find(';', end + 1)
|
ind, diff = start, 0
|
||||||
|
while end > 0:
|
||||||
|
diff += s.count('"', ind, end) - s.count('\\"', ind, end)
|
||||||
|
if diff % 2 == 0:
|
||||||
|
break
|
||||||
|
end, ind = ind, s.find(';', end + 1)
|
||||||
if end < 0:
|
if end < 0:
|
||||||
end = len(s)
|
end = len(s)
|
||||||
f = s[:end]
|
i = s.find('=', start, end)
|
||||||
if '=' in f:
|
if i == -1:
|
||||||
i = f.index('=')
|
f = s[start:end]
|
||||||
f = f[:i].strip().lower() + '=' + f[i+1:].strip()
|
else:
|
||||||
|
f = s[start:i].rstrip().lower() + '=' + s[i+1:end].lstrip()
|
||||||
plist.append(f.strip())
|
plist.append(f.strip())
|
||||||
s = s[end:]
|
start = end
|
||||||
return plist
|
return plist
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -481,6 +481,27 @@ class TestMessageAPI(TestEmailBase):
|
|||||||
"Content-Type: foo; bar*0=\"baz\\\"foobar\"; bar*1=\"\\\"baz\"")
|
"Content-Type: foo; bar*0=\"baz\\\"foobar\"; bar*1=\"\\\"baz\"")
|
||||||
self.assertEqual(msg.get_param('bar'), 'baz"foobar"baz')
|
self.assertEqual(msg.get_param('bar'), 'baz"foobar"baz')
|
||||||
|
|
||||||
|
def test_get_param_linear_complexity(self):
|
||||||
|
# Ensure that email.message._parseparam() is fast.
|
||||||
|
# See https://github.com/python/cpython/issues/136063.
|
||||||
|
N = 100_000
|
||||||
|
for s, r in [
|
||||||
|
("", ""),
|
||||||
|
("foo=bar", "foo=bar"),
|
||||||
|
(" FOO = bar ", "foo=bar"),
|
||||||
|
]:
|
||||||
|
with self.subTest(s=s, r=r, N=N):
|
||||||
|
src = f'{s};' * (N - 1) + s
|
||||||
|
res = email.message._parseparam(src)
|
||||||
|
self.assertEqual(len(res), N)
|
||||||
|
self.assertEqual(len(set(res)), 1)
|
||||||
|
self.assertEqual(res[0], r)
|
||||||
|
|
||||||
|
# This will be considered as a single parameter.
|
||||||
|
malformed = 's="' + ';' * (N - 1)
|
||||||
|
res = email.message._parseparam(malformed)
|
||||||
|
self.assertEqual(res, [malformed])
|
||||||
|
|
||||||
def test_field_containment(self):
|
def test_field_containment(self):
|
||||||
msg = email.message_from_string('Header: exists')
|
msg = email.message_from_string('Header: exists')
|
||||||
self.assertIn('header', msg)
|
self.assertIn('header', msg)
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
:mod:`email.message`: ensure linear complexity for legacy HTTP parameters
|
||||||
|
parsing. Patch by Bénédikt Tran.
|
||||||
Reference in New Issue
Block a user