[Python-checkins] bpo-43323: Fix UnicodeEncodeError in the email module (GH-32137)

serhiy-storchaka webhook-mailer at python.org
Sat Apr 30 06:17:28 EDT 2022


https://github.com/python/cpython/commit/e91dee87edcf6dee5dd78053004d76e5f05456d4
commit: e91dee87edcf6dee5dd78053004d76e5f05456d4
branch: main
author: Serhiy Storchaka <storchaka at gmail.com>
committer: serhiy-storchaka <storchaka at gmail.com>
date: 2022-04-30T13:17:23+03:00
summary:

bpo-43323: Fix UnicodeEncodeError in the email module (GH-32137)

It was raised if the charset itself contains characters not encodable
in UTF-8 (in particular \udcxx characters representing non-decodable
bytes in the source).

files:
A Misc/NEWS.d/next/Library/2022-03-27-12-40-16.bpo-43323.9mFPuI.rst
M Lib/email/_encoded_words.py
M Lib/email/_header_value_parser.py
M Lib/test/test_email/test__encoded_words.py
M Lib/test/test_email/test_email.py
M Lib/test/test_email/test_headerregistry.py

diff --git a/Lib/email/_encoded_words.py b/Lib/email/_encoded_words.py
index 295ae7eb21237..6795a606de037 100644
--- a/Lib/email/_encoded_words.py
+++ b/Lib/email/_encoded_words.py
@@ -179,15 +179,15 @@ def decode(ew):
     # Turn the CTE decoded bytes into unicode.
     try:
         string = bstring.decode(charset)
-    except UnicodeError:
+    except UnicodeDecodeError:
         defects.append(errors.UndecodableBytesDefect("Encoded word "
-            "contains bytes not decodable using {} charset".format(charset)))
+            f"contains bytes not decodable using {charset!r} charset"))
         string = bstring.decode(charset, 'surrogateescape')
-    except LookupError:
+    except (LookupError, UnicodeEncodeError):
         string = bstring.decode('ascii', 'surrogateescape')
         if charset.lower() != 'unknown-8bit':
-            defects.append(errors.CharsetError("Unknown charset {} "
-                "in encoded word; decoded as unknown bytes".format(charset)))
+            defects.append(errors.CharsetError(f"Unknown charset {charset!r} "
+                f"in encoded word; decoded as unknown bytes"))
     return string, charset, lang, defects
 
 
diff --git a/Lib/email/_header_value_parser.py b/Lib/email/_header_value_parser.py
index 51d355fbb0abc..8a8fb8bc42a95 100644
--- a/Lib/email/_header_value_parser.py
+++ b/Lib/email/_header_value_parser.py
@@ -781,7 +781,7 @@ def params(self):
                     else:
                         try:
                             value = value.decode(charset, 'surrogateescape')
-                        except LookupError:
+                        except (LookupError, UnicodeEncodeError):
                             # XXX: there should really be a custom defect for
                             # unknown character set to make it easy to find,
                             # because otherwise unknown charset is a silent
diff --git a/Lib/test/test_email/test__encoded_words.py b/Lib/test/test_email/test__encoded_words.py
index 0b8b1de3359aa..1713962f94cae 100644
--- a/Lib/test/test_email/test__encoded_words.py
+++ b/Lib/test/test_email/test__encoded_words.py
@@ -130,6 +130,13 @@ def test_unknown_charset(self):
                    # XXX Should this be a new Defect instead?
                    defects = [errors.CharsetError])
 
+    def test_invalid_character_in_charset(self):
+        self._test('=?utf-8\udce2\udc80\udc9d?q?foo=ACbar?=',
+                   b'foo\xacbar'.decode('ascii', 'surrogateescape'),
+                   charset = 'utf-8\udce2\udc80\udc9d',
+                   # XXX Should this be a new Defect instead?
+                   defects = [errors.CharsetError])
+
     def test_q_nonascii(self):
         self._test('=?utf-8?q?=C3=89ric?=',
                    'Éric',
diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py
index 933aa4cbc1959..69f883a3673f2 100644
--- a/Lib/test/test_email/test_email.py
+++ b/Lib/test/test_email/test_email.py
@@ -5356,6 +5356,15 @@ def test_rfc2231_unknown_encoding(self):
 Content-Transfer-Encoding: 8bit
 Content-Disposition: inline; filename*=X-UNKNOWN''myfile.txt
 
+"""
+        msg = email.message_from_string(m)
+        self.assertEqual(msg.get_filename(), 'myfile.txt')
+
+    def test_rfc2231_bad_character_in_encoding(self):
+        m = """\
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline; filename*=utf-8\udce2\udc80\udc9d''myfile.txt
+
 """
         msg = email.message_from_string(m)
         self.assertEqual(msg.get_filename(), 'myfile.txt')
diff --git a/Lib/test/test_email/test_headerregistry.py b/Lib/test/test_email/test_headerregistry.py
index 59fcd932e0ec4..25347ef13c214 100644
--- a/Lib/test/test_email/test_headerregistry.py
+++ b/Lib/test/test_email/test_headerregistry.py
@@ -714,6 +714,18 @@ def content_type_as_value(self,
             " charset*=unknown-8bit''utf-8%E2%80%9D\n",
             ),
 
+        'rfc2231_nonascii_in_charset_of_charset_parameter_value': (
+            "text/plain; charset*=utf-8”''utf-8%E2%80%9D",
+            'text/plain',
+            'text',
+            'plain',
+            {'charset': 'utf-8”'},
+            [],
+            'text/plain; charset="utf-8”"',
+            "Content-Type: text/plain;"
+            " charset*=utf-8''utf-8%E2%80%9D\n",
+            ),
+
         'rfc2231_encoded_then_unencoded_segments': (
             ('application/x-foo;'
                 '\tname*0*="us-ascii\'en-us\'My";'
diff --git a/Misc/NEWS.d/next/Library/2022-03-27-12-40-16.bpo-43323.9mFPuI.rst b/Misc/NEWS.d/next/Library/2022-03-27-12-40-16.bpo-43323.9mFPuI.rst
new file mode 100644
index 0000000000000..98d73101d3ee5
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-03-27-12-40-16.bpo-43323.9mFPuI.rst
@@ -0,0 +1,2 @@
+Fix errors in the :mod:`email` module if the charset itself contains
+undecodable/unencodable characters.



More information about the Python-checkins mailing list