[Python-checkins] bpo-33529, email: Fix infinite loop in email header encoding (GH-12020) (GH-14162)

Ned Deily webhook-mailer at python.org
Mon Jun 17 20:14:05 EDT 2019


https://github.com/python/cpython/commit/516a6a254814d2bc6a90290dfc44d77fdfb4050b
commit: 516a6a254814d2bc6a90290dfc44d77fdfb4050b
branch: 3.6
author: Victor Stinner <vstinner at redhat.com>
committer: Ned Deily <nad at python.org>
date: 2019-06-17T20:13:57-04:00
summary:

bpo-33529, email: Fix infinite loop in email header encoding (GH-12020) (GH-14162)

(cherry picked from commit c1f5667be1e3ec5871560c677402c1252c6018a6)

files:
A Misc/NEWS.d/next/Security/2019-02-24-18-48-16.bpo-33529.wpNNBD.rst
M Lib/email/_header_value_parser.py
M Lib/test/test_email/test_headerregistry.py
M Lib/test/test_email/test_policy.py

diff --git a/Lib/email/_header_value_parser.py b/Lib/email/_header_value_parser.py
index 1fb8cb448a01..f42cde203cef 100644
--- a/Lib/email/_header_value_parser.py
+++ b/Lib/email/_header_value_parser.py
@@ -2725,16 +2725,19 @@ def _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset):
             lines.append(' ')
             # XXX We'll get an infinite loop here if maxlen is <= 7
             continue
-        first_part = to_encode[:text_space]
-        ew = _ew.encode(first_part, charset=encode_as)
-        excess = len(ew) - remaining_space
-        if excess > 0:
-            # encode always chooses the shortest encoding, so this
-            # is guaranteed to fit at this point.
-            first_part = first_part[:-excess]
-            ew = _ew.encode(first_part)
-        lines[-1] += ew
-        to_encode = to_encode[len(first_part):]
+
+        to_encode_word = to_encode[:text_space]
+        encoded_word = _ew.encode(to_encode_word, charset=encode_as)
+        excess = len(encoded_word) - remaining_space
+        while excess > 0:
+            # Since the chunk to encode is guaranteed to fit into less than 100 characters,
+            # shrinking it by one at a time shouldn't take long.
+            to_encode_word = to_encode_word[:-1]
+            encoded_word = _ew.encode(to_encode_word, charset=encode_as)
+            excess = len(encoded_word) - remaining_space
+        lines[-1] += encoded_word
+        to_encode = to_encode[len(to_encode_word):]
+
         if to_encode:
             lines.append(' ')
             new_last_ew = len(lines[-1])
diff --git a/Lib/test/test_email/test_headerregistry.py b/Lib/test/test_email/test_headerregistry.py
index 30ce0ba54e47..d1007099f666 100644
--- a/Lib/test/test_email/test_headerregistry.py
+++ b/Lib/test/test_email/test_headerregistry.py
@@ -1643,10 +1643,10 @@ def test_fold_overlong_words_using_RFC2047(self):
         self.assertEqual(
             h.fold(policy=policy.default),
             'X-Report-Abuse: =?utf-8?q?=3Chttps=3A//www=2Emailitapp=2E'
-                'com/report=5F?=\n'
-            ' =?utf-8?q?abuse=2Ephp=3Fmid=3Dxxx-xxx-xxxx'
-                'xxxxxxxxxxxxxxxxxxxx=3D=3D-xxx-?=\n'
-            ' =?utf-8?q?xx-xx=3E?=\n')
+                'com/report=5Fabuse?=\n'
+            ' =?utf-8?q?=2Ephp=3Fmid=3Dxxx-xxx-xxxx'
+                'xxxxxxxxxxxxxxxxxxxx=3D=3D-xxx-xx-xx?=\n'
+            ' =?utf-8?q?=3E?=\n')
 
 
 if __name__ == '__main__':
diff --git a/Lib/test/test_email/test_policy.py b/Lib/test/test_email/test_policy.py
index 8fecb8a5fcd5..c2c437e6ac26 100644
--- a/Lib/test/test_email/test_policy.py
+++ b/Lib/test/test_email/test_policy.py
@@ -237,6 +237,14 @@ def test_adding_default_policies_preserves_default_factory(self):
                          email.policy.EmailPolicy.header_factory)
         self.assertEqual(newpolicy.__dict__, {'raise_on_defect': True})
 
+    def test_non_ascii_chars_do_not_cause_inf_loop(self):
+        policy = email.policy.default.clone(max_line_length=20)
+        actual = policy.fold('Subject', 'ą' * 12)
+        self.assertEqual(
+            actual,
+            'Subject: \n' +
+            12 * ' =?utf-8?q?=C4=85?=\n')
+
     # XXX: Need subclassing tests.
     # For adding subclassed objects, make sure the usual rules apply (subclass
     # wins), but that the order still works (right overrides left).
diff --git a/Misc/NEWS.d/next/Security/2019-02-24-18-48-16.bpo-33529.wpNNBD.rst b/Misc/NEWS.d/next/Security/2019-02-24-18-48-16.bpo-33529.wpNNBD.rst
new file mode 100644
index 000000000000..84d16f5a56ae
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2019-02-24-18-48-16.bpo-33529.wpNNBD.rst
@@ -0,0 +1,2 @@
+Prevent fold function used in email header encoding from entering infinite
+loop when there are too many non-ASCII characters in a header.



More information about the Python-checkins mailing list