]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.11] gh-136063: fix quadratic-complexity parsing in `email.message._parseparam... 3.11 98846/head
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Fri, 31 Oct 2025 17:29:53 +0000 (18:29 +0100)
committerGitHub <noreply@github.com>
Fri, 31 Oct 2025 17:29:53 +0000 (18:29 +0100)
(cherry picked from commit 680a5d070f59798bb88a1bb6eb027482b8d85c34)

Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
Lib/email/message.py
Lib/test/test_email/test_email.py
Misc/NEWS.d/next/Security/2025-06-28-13-23-53.gh-issue-136063.aGk0Jv.rst [new file with mode: 0644]

index 492a6b9a4309fafd1a025e2fa68fdb8770ece105..6a9903f9c8e84204216405d342efc5e9b0343241 100644 (file)
@@ -74,19 +74,25 @@ def _parseparam(s):
     # RDM This might be a Header, so for now stringify it.
     s = ';' + str(s)
     plist = []
-    while s[:1] == ';':
-        s = s[1:]
-        end = s.find(';')
-        while end > 0 and (s.count('"', 0, end) - s.count('\\"', 0, end)) % 2:
-            end = s.find(';', end + 1)
+    start = 0
+    while s.find(';', start) == start:
+        start += 1
+        end = s.find(';', start)
+        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:
             end = len(s)
-        f = s[:end]
-        if '=' in f:
-            i = f.index('=')
-            f = f[:i].strip().lower() + '=' + f[i+1:].strip()
+        i = s.find('=', start, end)
+        if i == -1:
+            f = s[start:end]
+        else:
+            f = s[start:i].rstrip().lower() + '=' + s[i+1:end].lstrip()
         plist.append(f.strip())
-        s = s[end:]
+        start = end
     return plist
 
 
index ad60ed3a7591c0c2b4ebf0ad90139b961b7169a0..431d362718ada78a50f2121b1e754c0acd01c3f3 100644 (file)
@@ -464,6 +464,27 @@ class TestMessageAPI(TestEmailBase):
             "Content-Type: foo; bar*0=\"baz\\\"foobar\"; bar*1=\"\\\"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):
         msg = email.message_from_string('Header: exists')
         self.assertIn('header', msg)
diff --git a/Misc/NEWS.d/next/Security/2025-06-28-13-23-53.gh-issue-136063.aGk0Jv.rst b/Misc/NEWS.d/next/Security/2025-06-28-13-23-53.gh-issue-136063.aGk0Jv.rst
new file mode 100644 (file)
index 0000000..940a3ad
--- /dev/null
@@ -0,0 +1,2 @@
+:mod:`email.message`: ensure linear complexity for legacy HTTP parameters
+parsing. Patch by Bénédikt Tran.