[Python-checkins] Fix infinite loop in email folding logic (GH-12732) (GH-14799)

Ned Deily webhook-mailer at python.org
Sun Jul 21 10:01:47 EDT 2019


https://github.com/python/cpython/commit/79a47e2b9cff6c9facdbc022a752177ab89dc533
commit: 79a47e2b9cff6c9facdbc022a752177ab89dc533
branch: 3.6
author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com>
committer: Ned Deily <nad at python.org>
date: 2019-07-21T16:01:43+02:00
summary:

Fix infinite loop in email folding logic (GH-12732) (GH-14799)

As far as I can tell, this infinite loop would be triggered if:

1. The value being folded contains a single word (no spaces) longer than
   max_line_length
2. The max_line_length is shorter than the encoding's name + 9
   characters.

bpo-36564: https://bugs.python.org/issue36564
(cherry picked from commit f69d5c61981ea97d251db515c7ff280fcc17182d)

Co-authored-by: Paul Ganssle <pganssle at users.noreply.github.com>

files:
A Misc/NEWS.d/next/Library/2019-04-08-13-00-13.bpo-36564._n67m_.rst
M Lib/email/_header_value_parser.py
M Lib/email/parser.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 f42cde203cef..e3c343dffb42 100644
--- a/Lib/email/_header_value_parser.py
+++ b/Lib/email/_header_value_parser.py
@@ -2715,15 +2715,22 @@ def _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset):
         trailing_wsp = to_encode[-1]
         to_encode = to_encode[:-1]
     new_last_ew = len(lines[-1]) if last_ew is None else last_ew
+
+    encode_as = 'utf-8' if charset == 'us-ascii' else charset
+
+    # The RFC2047 chrome takes up 7 characters plus the length
+    # of the charset name.
+    chrome_len = len(encode_as) + 7
+
+    if (chrome_len + 1) >= maxlen:
+        raise errors.HeaderParseError(
+            "max_line_length is too small to fit an encoded word")
+
     while to_encode:
         remaining_space = maxlen - len(lines[-1])
-        # The RFC2047 chrome takes up 7 characters plus the length
-        # of the charset name.
-        encode_as = 'utf-8' if charset == 'us-ascii' else charset
-        text_space = remaining_space - len(encode_as) - 7
+        text_space = remaining_space - chrome_len
         if text_space <= 0:
             lines.append(' ')
-            # XXX We'll get an infinite loop here if maxlen is <= 7
             continue
 
         to_encode_word = to_encode[:text_space]
diff --git a/Lib/email/parser.py b/Lib/email/parser.py
index 555b17256061..7db4da1ff081 100644
--- a/Lib/email/parser.py
+++ b/Lib/email/parser.py
@@ -13,7 +13,6 @@
 from email._policybase import compat32
 
 
-

 class Parser:
     def __init__(self, _class=None, *, policy=compat32):
         """Parser of RFC 2822 and MIME email messages.
diff --git a/Lib/test/test_email/test_policy.py b/Lib/test/test_email/test_policy.py
index c2c437e6ac26..6999e4af10d9 100644
--- a/Lib/test/test_email/test_policy.py
+++ b/Lib/test/test_email/test_policy.py
@@ -2,6 +2,7 @@
 import types
 import textwrap
 import unittest
+import email.errors
 import email.policy
 import email.parser
 import email.generator
@@ -245,6 +246,25 @@ def test_non_ascii_chars_do_not_cause_inf_loop(self):
             'Subject: \n' +
             12 * ' =?utf-8?q?=C4=85?=\n')
 
+    def test_short_maxlen_error(self):
+        # RFC 2047 chrome takes up 7 characters, plus the length of the charset
+        # name, so folding should fail if maxlen is lower than the minimum
+        # required length for a line.
+
+        # Note: This is only triggered when there is a single word longer than
+        # max_line_length, hence the 1234567890 at the end of this whimsical
+        # subject. This is because when we encounter a word longer than
+        # max_line_length, it is broken down into encoded words to fit
+        # max_line_length. If the max_line_length isn't large enough to even
+        # contain the RFC 2047 chrome (`?=<charset>?q??=`), we fail.
+        subject = "Melt away the pounds with this one simple trick! 1234567890"
+
+        for maxlen in [3, 7, 9]:
+            with self.subTest(maxlen=maxlen):
+                policy = email.policy.default.clone(max_line_length=maxlen)
+                with self.assertRaises(email.errors.HeaderParseError):
+                    policy.fold("Subject", subject)
+
     # 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/Library/2019-04-08-13-00-13.bpo-36564._n67m_.rst b/Misc/NEWS.d/next/Library/2019-04-08-13-00-13.bpo-36564._n67m_.rst
new file mode 100644
index 000000000000..ddd17aec1dd8
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-04-08-13-00-13.bpo-36564._n67m_.rst
@@ -0,0 +1,3 @@
+Fix infinite loop in email header folding logic that would be triggered when
+an email policy's max_line_length is not long enough to include the required
+markup and any values in the message. Patch by Paul Ganssle



More information about the Python-checkins mailing list